<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <author>
    <name>Last</name>
  </author>
  <generator uri="https://hexo.io/">Hexo</generator>
  <id>https://blog.imlast.top/</id>
  <link href="https://blog.imlast.top/" rel="alternate"/>
  <link href="https://blog.imlast.top/atom.xml" rel="self"/>
  <rights>All rights reserved 2026, Last</rights>
  <subtitle>void of myself</subtitle>
  <title>Last Blog</title>
  <updated>2026-04-01T08:39:00.000Z</updated>
  <entry>
    <author>
      <name>Last</name>
    </author>
    <category term="笔记" scheme="https://blog.imlast.top/categories/%E7%AC%94%E8%AE%B0/"/>
    <category term="browser" scheme="https://blog.imlast.top/tags/browser/"/>
    <category term="wxt" scheme="https://blog.imlast.top/tags/wxt/"/>
    <content>
      <![CDATA[<h1 id="textarea-自动伸缩"><a href="#textarea-自动伸缩" class="headerlink" title="textarea 自动伸缩"></a>textarea 自动伸缩</h1><ul><li><p>一个简单的需求：我们希望当用户输入的提示词长度过大时，textarea 的高度能随之伸展，将所有提示词内容显示出来。</p></li><li><p>相关代码：</p>  <div class="code-container" data-rel="Ts"><figure class="iseeu highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">adjustUI</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">if</span> (!textareaRef) &#123;</span><br><span class="line">        logger.<span class="title function_">error</span>(<span class="string">&quot; textareaRef is undefined, unable to reset textarea height. &quot;</span>);</span><br><span class="line">        <span class="keyword">return</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    textareaRef.<span class="property">style</span>.<span class="property">height</span> = <span class="string">&quot;auto&quot;</span>;</span><br><span class="line">    textareaRef.<span class="property">style</span>.<span class="property">height</span> = <span class="string">`<span class="subst">$&#123;textareaRef.scrollHeight &lt;= <span class="number">500</span> ? textareaRef.scrollHeight : <span class="number">500</span>&#125;</span>px`</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div><ul><li><p>其中，<code>textareaRef</code> 是通过 <code>bind</code> 和 textarea element 绑定在一起的：</p>  <div class="code-container" data-rel="Ts"><figure class="iseeu highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">&lt;textarea</span><br><span class="line">    <span class="attr">bind</span>:<span class="variable language_">this</span>=&#123;textareaRef&#125;</span><br><span class="line">    oninput=&#123;adjustUI&#125;</span><br><span class="line">    <span class="comment">// ...</span></span><br><span class="line">&gt;&lt;/textarea&gt;</span><br></pre></td></tr></table></figure></div></li></ul></li><li><p>同时，在用户提交输入之后需要将输入框复原：</p>  <div class="code-container" data-rel="Ts"><figure class="iseeu highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">textareaRef.<span class="property">style</span>.<span class="property">height</span> = <span class="string">&quot;auto&quot;</span>;</span><br></pre></td></tr></table></figure></div></li></ul><h1 id="LLM-回复状态层"><a href="#LLM-回复状态层" class="headerlink" title="LLM 回复状态层"></a>LLM 回复状态层</h1><ul><li><p>期初的问题是：如何在用户发送消息之后在 UI 上渲染一个 Skeleton 消息来向用户表示“正在请求回复”。</p></li><li><p>一开始我是直接在消息数组中插入一个假消息作为 placeholder：</p>  <div class="code-container" data-rel="Ts"><figure class="iseeu highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">&#123;</span><br><span class="line">    <span class="attr">id</span>: messageId,</span><br><span class="line">    <span class="attr">role</span>: <span class="string">&quot;assistant&quot;</span>,</span><br><span class="line">    <span class="attr">timestamp</span>: <span class="title function_">getCurrentTimestamp</span>(),</span><br><span class="line">    <span class="attr">content</span>: <span class="string">&quot;Fetching response...&quot;</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div></li><li><p>然后在 <code>Chat.svelte</code> 中判断 <code>messages</code> 的最后一条消息 <code>content</code> 是否为 “Fetching response…”，如果是，那么就将他渲染为 Placeholder 组件。</p></li><li><p>很显然，这是个非常丑陋的方案。我在与 ChatGPT 探讨一番后定下了一个设计：</p>  <div class="code-container" data-rel="Ts"><figure class="iseeu highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="keyword">type</span> <span class="title class_">LLMResponseState</span> = &#123;</span><br><span class="line">    <span class="attr">messageId</span>: <span class="built_in">string</span>;</span><br><span class="line">    <span class="attr">phase</span>: <span class="string">&quot;idle&quot;</span> | <span class="string">&quot;pending&quot;</span> | <span class="string">&quot;streaming&quot;</span> | <span class="string">&quot;error&quot;</span>;</span><br><span class="line">    <span class="attr">error</span>: <span class="built_in">string</span>;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure></div></li><li><p>在父组件 <code>App.svelte</code> 中定义一个新的 state 来追踪当前 LLM 的回复状态。这样一来，不只是等待回复中的 UI 提示便于设计，整个插件应用在用户的使用周期中的行为顺序设定也有了一个“抓手”。</p></li></ul><details class="relative my-4 border border-border-color bg-second-background-color rounded-md  green" data-header-exclude><summary class="px-4 py-2 rounded-md shadow-[0_0_2px_0_var(--shadow-color-1)] cursor-pointer not-markdown"><i class="fa-solid fa-chevron-right"></i>一个简单的流程示意图</summary><div class="content p-4 "><pre class="mermaid">flowchart TDA[idle] -->|_user submits prompt_| B[pending]B -->|user message already added \n assistantMessageId reserved \n no assistant MessageFeed yet \n `Chat.svelte` renders visual placeholder| C[streaming: first chunk]C -->| create assistant MessageFeed with reserved id \n content starts as '' \n append first chunk \n Chat now renders the real assistant message | D[streaming: more chunks]D -->|append chunk by messageId| E[idle]</pre></div></details><ul><li><p>此外，<code>callLLM</code> 函数中接受一个 <code>callback</code> 对象：</p>  <div class="code-container" data-rel="Ts"><figure class="iseeu highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="keyword">type</span> <span class="title class_">LLMCallback</span> = &#123;</span><br><span class="line">    <span class="attr">onStream</span>: <span class="function">(<span class="params"><span class="attr">chunk</span>: <span class="built_in">string</span></span>) =&gt;</span> <span class="built_in">void</span>;</span><br><span class="line">    <span class="attr">onDone</span>: <span class="function">() =&gt;</span> <span class="built_in">void</span>;</span><br><span class="line">    <span class="attr">onError</span>: <span class="function">(<span class="params"><span class="attr">error</span>: <span class="built_in">string</span></span>) =&gt;</span> <span class="built_in">void</span>;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure></div><ol><li><code>onStream</code> 负责将流式传输获取的 <code>chunk</code> 填入当前接收 LLM 消息输出的 <code>MessageFeed</code>，再籍由 Svelte 5 提供的深度状态机制自动触发 UI 更新</li><li><code>onDone</code> 负责在每条消息传输完成之后将对话内容持久化，即存储到 localStorage</li><li><code>onError</code> 负责触发 error 信息。（ <em>目前还未实现错误处理的 UI</em> ）</li></ol></li><li><p>这样一来，一次请求的周期，以及各自的状态变更和相对应的流程就非常明确了。</p></li></ul><h1 id="对话内容的持久化存储"><a href="#对话内容的持久化存储" class="headerlink" title="对话内容的持久化存储"></a>对话内容的持久化存储</h1><ul><li><p>wxt 提供了一个 <code>storage</code> 模块，可以操作浏览器插件的 <code>browser.storage.local</code>：</p>  <div class="code-container" data-rel="Ts"><figure class="iseeu highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; storage &#125; <span class="keyword">from</span> <span class="string">&quot;wxt/utils/storage&quot;</span>;</span><br></pre></td></tr></table></figure></div></li><li><p>目前插件对于用户创建对话的处理流程是：</p>  <pre class="mermaid">    flowchart TD  A[no conversation] --->|user input| B[add user prompt \n **store conversation**]  B --->|stream llm message| C[stream llm message chunks into messageFeed \n **store conversation**]</pre></li><li><p>可以看到，存储对话信息的时间在 <strong>用户消息发送之后</strong> ，以及 <strong>LLM 消息传输完成之后</strong> 。这意味着如果用户在流式传输的过程中关闭插件侧板，将会导致流式传输中断，并且不会触发保存机制。</p></li><li><p>目前来说，这是预期内行为，因为我感觉将 API 请求和流式传输搬到 <code>background.ts</code> 实在是有些笨重。或许将来会考虑这样做。</p></li></ul><hr><ul><li><p>值得一提的是，浏览器插件的 storage 毕竟不是数据库，并不支持 <em>根据 <code>sessionId</code> 来存取对应的 <code>Conversation</code></em> 这种行为，因此笔者决定采用 Key Prefixing 的方式——直接将 <code>sessionId</code> 当作 <code>key</code> 存在 Storage 中：</p>  <div class="code-container" data-rel="Ts"><figure class="iseeu highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="keyword">function</span> <span class="title function_">getConversationKey</span>(<span class="params"><span class="attr">sessionId</span>: UUID</span>): <span class="string">`local:<span class="subst">$&#123;<span class="built_in">string</span>&#125;</span>`</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="string">`local:conversation:<span class="subst">$&#123;sessionId&#125;</span>`</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div></li><li><p>这样一来就可以在某种程度上实现 <em>根据 <code>sessionId</code> 来存取对应的 <code>Conversation</code></em> 的行为。</p></li></ul><h1 id="Streaming-消息的流式传输"><a href="#Streaming-消息的流式传输" class="headerlink" title="Streaming 消息的流式传输"></a>Streaming 消息的流式传输</h1><ul><li><p>流式传输——这是一个 LLM 应用前端的“典中典”功能，其在 <code>OpenAI</code> 官方提供的第三方库的帮助下，实现意外的简单。</p></li><li><p><code>callback</code> 函数中的 <code>onStream</code> 如下所示：</p>  <div class="code-container" data-rel="Ts"><figure class="iseeu highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> messageId the target message feed reserved for LLM response</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> chunk new arrived chunk ready for streaming</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@description</span> update message with id `messageId` in `messageFeed` by adding `chunk` to its content</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">streamMessage</span>(<span class="params"><span class="attr">messageId</span>: UUID, <span class="attr">chunk</span>: <span class="built_in">string</span></span>) &#123;</span><br><span class="line">    <span class="keyword">const</span> index = conversation?.<span class="property">messages</span>.<span class="title function_">findIndex</span>(<span class="function">(<span class="params">m</span>) =&gt;</span> m.<span class="property">id</span> === messageId);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (index === -<span class="number">1</span>) &#123;</span><br><span class="line">        logger.<span class="title function_">error</span>(<span class="string">&quot;target messageId does not exist: &quot;</span>, messageId);</span><br><span class="line">        <span class="keyword">return</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    llmResponse.<span class="property">phase</span> = <span class="string">&quot;streaming&quot;</span>;</span><br><span class="line">    <span class="comment">// <span class="doctag">NOTE:</span> requires svelte 5 deep state</span></span><br><span class="line">    conversation.<span class="property">messages</span>[index].<span class="property">content</span> += chunk;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div></li><li><p>至于 <code>callLLM</code> 中的流式传输逻辑：</p>  <div class="code-container" data-rel="Ts"><figure class="iseeu highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// LLM API integration</span></span><br><span class="line"><span class="keyword">try</span> &#123;</span><br><span class="line">    <span class="keyword">const</span> stream = <span class="keyword">await</span> client.<span class="property">chat</span>.<span class="property">completions</span>.<span class="title function_">create</span>(&#123;</span><br><span class="line">        <span class="attr">model</span>: params.<span class="property">modelName</span>,</span><br><span class="line">        <span class="attr">messages</span>: completeContext,</span><br><span class="line">        <span class="attr">stream</span>: <span class="literal">true</span></span><br><span class="line">    &#125;);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// update message with LLM stream response</span></span><br><span class="line">    <span class="keyword">for</span> <span class="title function_">await</span> (<span class="keyword">const</span> chunk <span class="keyword">of</span> stream) &#123;</span><br><span class="line">        <span class="keyword">const</span> chunkContent = chunk.<span class="property">choices</span>[<span class="number">0</span>]?.<span class="property">delta</span>?.<span class="property">content</span> || <span class="string">&quot;&quot;</span>;</span><br><span class="line">        <span class="keyword">if</span> (chunkContent) params.<span class="property">callback</span>.<span class="title function_">onStream</span>(chunkContent);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    params.<span class="property">callback</span>.<span class="title function_">onDone</span>();</span><br><span class="line">&#125; <span class="keyword">catch</span> (error) &#123;</span><br><span class="line">    params.<span class="property">callback</span>.<span class="title function_">onError</span>(<span class="title class_">String</span>(error));</span><br><span class="line">    <span class="keyword">throw</span> error;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div><p>  <em><code>client</code> 是一个 <code>OpenAI</code> 对象</em></p></li><li><p>我还真是第一次在 js&#x2F;ts 中遇到这种“异步循环” <code>for await</code>，看了一下 <a class="link"   href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for-await...of" >MDN<i class="fa-solid fa-arrow-up-right ml-[0.2em] font-light align-text-top text-[0.7em] link-icon"></i></a>：</p><blockquote><p>The for <code>await...of</code> statement creates a loop iterating over async iterable objects as well as sync iterables. This statement can only be used in contexts where <code>await</code> can be used, which includes inside an async function body and in a module.</p></blockquote></li><li><p>倘若如此的话，<code>stream</code> 应该是一个 <em>async iterable object</em> ？查看其类型，发现是 <code>Stream&lt;ChatCompletionChunk&gt; &amp; &#123; _request_id?: string | null | undefined &#125;</code>。</p></li><li><p>翻看 <a class="link"   href="https://developers.openai.com/api/reference/resources/completions/methods/create" >OpenAI 文档<i class="fa-solid fa-arrow-up-right ml-[0.2em] font-light align-text-top text-[0.7em] link-icon"></i></a> 得知参数 <code>stream</code> ：</p><blockquote><p>stream: optional boolean<br>Whether to stream back partial progress. If set, tokens will be sent as data-only <a class="link"   href="https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events#Event_stream_format" >server-sent events<i class="fa-solid fa-arrow-up-right ml-[0.2em] font-light align-text-top text-[0.7em] link-icon"></i></a> as they become available, with the stream terminated by a <code>data: [DONE]</code> message.</p></blockquote></li><li><p>所以简而言之，<code>client.chat.completions.create</code> 中将 <code>stream</code> 参数设置为 <code>true</code> 即可让其返回在 resolve 后返回一个特殊的 <code>iterable object</code> (<code>Stream&lt;ChatCompletionChunk&gt;</code>) 的 <code>APIPromise</code>；对这个特殊的可迭代对象使用 <code>for-await</code> 循环即可渐进的取得流式传输的输出。</p></li></ul><details class="relative my-4 border border-border-color bg-second-background-color rounded-md  blue" data-header-exclude><summary class="px-4 py-2 rounded-md shadow-[0_0_2px_0_var(--shadow-color-1)] cursor-pointer not-markdown"><i class="fa-solid fa-chevron-right"></i>已严肃学习</summary><div class="content p-4 "><ul><li>瞅了一眼 <code>openai</code> 库源码，发现 <code>Stream</code> 实例对象确实具有一个 <code>iterator</code> 属性：</li></ul><div class="code-container" data-rel="Ts"><figure class="iseeu highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="keyword">class</span> <span class="title class_">Stream</span>&lt;<span class="title class_">Item</span>&gt; <span class="keyword">implements</span> <span class="title class_">AsyncIterable</span>&lt;<span class="title class_">Item</span>&gt; &#123;</span><br><span class="line"><span class="attr">controller</span>: <span class="title class_">AbortController</span>;</span><br><span class="line">#<span class="attr">client</span>: <span class="title class_">OpenAI</span> | <span class="literal">undefined</span>;</span><br><span class="line"></span><br><span class="line"><span class="title function_">constructor</span>(<span class="params"></span></span><br><span class="line"><span class="params">    <span class="keyword">private</span> <span class="attr">iterator</span>: () =&gt; <span class="title class_">AsyncIterator</span>&lt;<span class="title class_">Item</span>&gt;,</span></span><br><span class="line"><span class="params">    <span class="attr">controller</span>: <span class="title class_">AbortController</span>,</span></span><br><span class="line"><span class="params">    <span class="attr">client</span>?: <span class="title class_">OpenAI</span>,</span></span><br><span class="line"><span class="params"></span>) &#123;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">controller</span> = controller;</span><br><span class="line">    <span class="variable language_">this</span>.#client = client;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div><ul><li>再看一眼 <code>client.chat.completions.create</code> 方法的源码：</li></ul><div class="code-container" data-rel="Ts"><figure class="iseeu highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="keyword">class</span> <span class="title class_">Completions</span> <span class="keyword">extends</span> <span class="title class_ inherited__">APIResource</span> &#123;</span><br><span class="line"><span class="attr">messages</span>: <span class="title class_">MessagesAPI</span>.<span class="property">Messages</span> = <span class="keyword">new</span> <span class="title class_">MessagesAPI</span>.<span class="title class_">Messages</span>(<span class="variable language_">this</span>.<span class="property">_client</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment">* Creates a model response for the given chat conversation. Learn more in the</span></span><br><span class="line"><span class="comment">* [text generation](https://platform.openai.com/docs/guides/text-generation),</span></span><br><span class="line"><span class="comment">* [vision](https://platform.openai.com/docs/guides/vision), and</span></span><br><span class="line"><span class="comment">* [audio](https://platform.openai.com/docs/guides/audio) guides.</span></span><br><span class="line"><span class="comment">*</span></span><br><span class="line"><span class="comment">* Returns a chat completion object, or a streamed sequence of chat completion</span></span><br><span class="line"><span class="comment">* chunk objects if the request is streamed.</span></span><br><span class="line"><span class="comment">*/</span></span><br><span class="line"><span class="title function_">create</span>(<span class="attr">body</span>: <span class="title class_">ChatCompletionCreateParamsNonStreaming</span>, <span class="attr">options</span>?: <span class="title class_">RequestOptions</span>): <span class="title class_">APIPromise</span>&lt;<span class="title class_">ChatCompletion</span>&gt;;</span><br><span class="line"><span class="title function_">create</span>(</span><br><span class="line">    <span class="attr">body</span>: <span class="title class_">ChatCompletionCreateParamsStreaming</span>,</span><br><span class="line">    <span class="attr">options</span>?: <span class="title class_">RequestOptions</span>,</span><br><span class="line">): <span class="title class_">APIPromise</span>&lt;<span class="title class_">Stream</span>&lt;<span class="title class_">ChatCompletionChunk</span>&gt;&gt;;</span><br><span class="line"><span class="title function_">create</span>(</span><br><span class="line">    <span class="attr">body</span>: <span class="title class_">ChatCompletionCreateParamsBase</span>,</span><br><span class="line">    <span class="attr">options</span>?: <span class="title class_">RequestOptions</span>,</span><br><span class="line">): <span class="title class_">APIPromise</span>&lt;<span class="title class_">Stream</span>&lt;<span class="title class_">ChatCompletionChunk</span>&gt; | <span class="title class_">ChatCompletion</span>&gt;;</span><br><span class="line"><span class="title function_">create</span>(</span><br><span class="line">    <span class="attr">body</span>: <span class="title class_">ChatCompletionCreateParams</span>,</span><br><span class="line">    <span class="attr">options</span>?: <span class="title class_">RequestOptions</span>,</span><br><span class="line">): <span class="title class_">APIPromise</span>&lt;<span class="title class_">ChatCompletion</span>&gt; | <span class="title class_">APIPromise</span>&lt;<span class="title class_">Stream</span>&lt;<span class="title class_">ChatCompletionChunk</span>&gt;&gt; &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="variable language_">this</span>.<span class="property">_client</span>.<span class="title function_">post</span>(<span class="string">&#x27;/chat/completions&#x27;</span>, &#123; body, ...options, <span class="attr">stream</span>: body.<span class="property">stream</span> ?? <span class="literal">false</span> &#125;) <span class="keyword">as</span></span><br><span class="line">    | <span class="title class_">APIPromise</span>&lt;<span class="title class_">ChatCompletion</span>&gt;</span><br><span class="line">    | <span class="title class_">APIPromise</span>&lt;<span class="title class_">Stream</span>&lt;<span class="title class_">ChatCompletionChunk</span>&gt;&gt;;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div><div class="code-container" data-rel="Ts"><figure class="iseeu highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="keyword">interface</span> <span class="title class_">ChatCompletionCreateParamsStreaming</span> <span class="keyword">extends</span> <span class="title class_">ChatCompletionCreateParamsBase</span> &#123;</span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * If set to true, the model response data will be streamed to the client as it is</span></span><br><span class="line"><span class="comment">     * generated using</span></span><br><span class="line"><span class="comment">     * [server-sent events](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events#Event_stream_format).</span></span><br><span class="line"><span class="comment">     * See the</span></span><br><span class="line"><span class="comment">     * [Streaming section below](https://platform.openai.com/docs/api-reference/chat/streaming)</span></span><br><span class="line"><span class="comment">     * for more information, along with the</span></span><br><span class="line"><span class="comment">     * [streaming responses](https://platform.openai.com/docs/guides/streaming-responses)</span></span><br><span class="line"><span class="comment">     * guide for more information on how to handle the streaming events.</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="attr">stream</span>: <span class="literal">true</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div><ul><li><p>观察函数重载的签名得知，<code>stream</code> 项设置为 <code>true</code> 的时候，<code>chat.completions.create</code> 将返回一个 <code>APIPromise&lt;Stream&lt;ChatCompletionChunk&gt;&gt;</code>。</p></li><li><p>这个 OpenAI 自定义的 Promise 重写了<code>then</code> 函数：</p></li></ul><div class="code-container" data-rel="Ts"><figure class="iseeu highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> <span class="title function_">parse</span>(): <span class="title class_">Promise</span>&lt;<span class="title class_">WithRequestID</span>&lt;T&gt;&gt; &#123;</span><br><span class="line">    <span class="keyword">if</span> (!<span class="variable language_">this</span>.<span class="property">parsedPromise</span>) &#123;</span><br><span class="line">        <span class="variable language_">this</span>.<span class="property">parsedPromise</span> = <span class="variable language_">this</span>.<span class="property">responsePromise</span>.<span class="title function_">then</span>(<span class="function">(<span class="params">data</span>) =&gt;</span></span><br><span class="line">            <span class="variable language_">this</span>.<span class="title function_">parseResponse</span>(<span class="variable language_">this</span>.#client, data),</span><br><span class="line">        ) <span class="keyword">as</span> <span class="built_in">any</span> <span class="keyword">as</span> <span class="title class_">Promise</span>&lt;<span class="title class_">WithRequestID</span>&lt;T&gt;&gt;;</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">return</span> <span class="variable language_">this</span>.<span class="property">parsedPromise</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">override</span> then&lt;<span class="title class_">TResult1</span> = <span class="title class_">WithRequestID</span>&lt;T&gt;, <span class="title class_">TResult2</span> = <span class="built_in">never</span>&gt;(</span><br><span class="line">    <span class="attr">onfulfilled</span>?: (<span class="function">(<span class="params"><span class="attr">value</span>: <span class="title class_">WithRequestID</span>&lt;T&gt;</span>) =&gt;</span> <span class="title class_">TResult1</span> | <span class="title class_">PromiseLike</span>&lt;<span class="title class_">TResult1</span>&gt;) | <span class="literal">undefined</span> | <span class="literal">null</span>,</span><br><span class="line">    <span class="attr">onrejected</span>?: (<span class="function">(<span class="params"><span class="attr">reason</span>: <span class="built_in">any</span></span>) =&gt;</span> <span class="title class_">TResult2</span> | <span class="title class_">PromiseLike</span>&lt;<span class="title class_">TResult2</span>&gt;) | <span class="literal">undefined</span> | <span class="literal">null</span>,</span><br><span class="line">): <span class="title class_">Promise</span>&lt;<span class="title class_">TResult1</span> | <span class="title class_">TResult2</span>&gt; &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="variable language_">this</span>.<span class="title function_">parse</span>().<span class="title function_">then</span>(onfulfilled, onrejected);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div><ul><li>那么这个 <code>this.parseReponse</code> 函数是哪里传过来的呢？根据此前 <code>create</code> 的源码我们知道其调用了 <code>this._client.post(...)</code>，具体来说：</li></ul><div class="code-container" data-rel="Ts"><figure class="iseeu highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment">* API Client for interfacing with the OpenAI API.</span></span><br><span class="line"><span class="comment">*/</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">class</span> <span class="title class_">OpenAI</span> &#123;</span><br><span class="line">    <span class="attr">apiKey</span>: <span class="built_in">string</span>;</span><br><span class="line">    <span class="attr">organization</span>: <span class="built_in">string</span> | <span class="literal">null</span>;</span><br><span class="line">    <span class="attr">project</span>: <span class="built_in">string</span> | <span class="literal">null</span>;</span><br><span class="line">    <span class="comment">// ...</span></span><br><span class="line"></span><br><span class="line">    post&lt;<span class="title class_">Rsp</span>&gt;(<span class="attr">path</span>: <span class="built_in">string</span>, <span class="attr">opts</span>?: <span class="title class_">PromiseOrValue</span>&lt;<span class="title class_">RequestOptions</span>&gt;): <span class="title class_">APIPromise</span>&lt;<span class="title class_">Rsp</span>&gt; &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="variable language_">this</span>.<span class="title function_">methodRequest</span>(<span class="string">&#x27;post&#x27;</span>, path, opts);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> methodRequest&lt;<span class="title class_">Rsp</span>&gt;(</span><br><span class="line">        <span class="attr">method</span>: <span class="title class_">HTTPMethod</span>,</span><br><span class="line">        <span class="attr">path</span>: <span class="built_in">string</span>,</span><br><span class="line">        <span class="attr">opts</span>?: <span class="title class_">PromiseOrValue</span>&lt;<span class="title class_">RequestOptions</span>&gt;,</span><br><span class="line">    ): <span class="title class_">APIPromise</span>&lt;<span class="title class_">Rsp</span>&gt; &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="variable language_">this</span>.<span class="title function_">request</span>(</span><br><span class="line">            <span class="title class_">Promise</span>.<span class="title function_">resolve</span>(opts).<span class="title function_">then</span>(<span class="function">(<span class="params">opts</span>) =&gt;</span> &#123;</span><br><span class="line">                <span class="keyword">return</span> &#123; method, path, ...opts &#125;;</span><br><span class="line">            &#125;),</span><br><span class="line">        );</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    request&lt;<span class="title class_">Rsp</span>&gt;(</span><br><span class="line">        <span class="attr">options</span>: <span class="title class_">PromiseOrValue</span>&lt;<span class="title class_">FinalRequestOptions</span>&gt;,</span><br><span class="line">        <span class="attr">remainingRetries</span>: <span class="built_in">number</span> | <span class="literal">null</span> = <span class="literal">null</span>,</span><br><span class="line">    ): <span class="title class_">APIPromise</span>&lt;<span class="title class_">Rsp</span>&gt; &#123;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">APIPromise</span>(<span class="variable language_">this</span>, <span class="variable language_">this</span>.<span class="title function_">makeRequest</span>(options, remainingRetries, <span class="literal">undefined</span>));</span><br><span class="line">    &#125;</span><br></pre></td></tr></table></figure></div><ul><li>再观察 <code>APIPromise</code> 类的构造函数：</li></ul><div class="code-container" data-rel="Ts"><figure class="iseeu highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="keyword">class</span> <span class="title class_">APIPromise</span>&lt;T&gt; <span class="keyword">extends</span> <span class="title class_">Promise</span>&lt;<span class="title class_">WithRequestID</span>&lt;T&gt;&gt; &#123;</span><br><span class="line"><span class="keyword">private</span> <span class="attr">parsedPromise</span>: <span class="title class_">Promise</span>&lt;<span class="title class_">WithRequestID</span>&lt;T&gt;&gt; | <span class="literal">undefined</span>;</span><br><span class="line">#<span class="attr">client</span>: <span class="title class_">OpenAI</span>;</span><br><span class="line">    <span class="title function_">constructor</span>(<span class="params"></span></span><br><span class="line"><span class="params">        <span class="attr">client</span>: <span class="title class_">OpenAI</span>,</span></span><br><span class="line"><span class="params">        <span class="keyword">private</span> <span class="attr">responsePromise</span>: <span class="title class_">Promise</span>&lt;<span class="title class_">APIResponseProps</span>&gt;,</span></span><br><span class="line"><span class="params">        <span class="keyword">private</span> <span class="attr">parseResponse</span>: (</span></span><br><span class="line"><span class="params">            client: OpenAI,</span></span><br><span class="line"><span class="params">            props: APIResponseProps,</span></span><br><span class="line"><span class="params">        ) =&gt; <span class="title class_">PromiseOrValue</span>&lt;<span class="title class_">WithRequestID</span>&lt;T&gt;&gt; = defaultParseResponse,</span></span><br><span class="line"><span class="params">    </span>) &#123;</span><br><span class="line">        <span class="variable language_">super</span>(<span class="function">(<span class="params">resolve</span>) =&gt;</span> &#123;</span><br><span class="line">            <span class="comment">// this is maybe a bit weird but this has to be a no-op to not implicitly</span></span><br><span class="line">            <span class="comment">// parse the response body; instead .then, .catch, .finally are overridden</span></span><br><span class="line">            <span class="comment">// to parse the response</span></span><br><span class="line">            <span class="title function_">resolve</span>(<span class="literal">null</span> <span class="keyword">as</span> <span class="built_in">any</span>);</span><br><span class="line">        &#125;);</span><br><span class="line">        <span class="variable language_">this</span>.#client = client;</span><br><span class="line">    &#125;</span><br></pre></td></tr></table></figure></div><ul><li>可以看到，<code>create</code> 函数调用之后的 <code>request</code> 函数中返回一个新的 <code>APIPromise</code> 对象的时候，没有传入 <code>parseResponse</code> 函数，因此其被定义为 <code>defaultParseResponse</code>：</li></ul><div class="code-container" data-rel="Ts"><figure class="iseeu highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="keyword">async</span> <span class="keyword">function</span> defaultParseResponse&lt;T&gt;(</span><br><span class="line">    <span class="attr">client</span>: <span class="title class_">OpenAI</span>,</span><br><span class="line">    <span class="attr">props</span>: <span class="title class_">APIResponseProps</span>,</span><br><span class="line">): <span class="title class_">Promise</span>&lt;<span class="title class_">WithRequestID</span>&lt;T&gt;&gt; &#123;</span><br><span class="line">    <span class="keyword">const</span> &#123; response, requestLogID, retryOfRequestLogID, startTime &#125; = props;</span><br><span class="line">    <span class="keyword">const</span> body = <span class="title function_">await</span> (<span class="title function_">async</span> () =&gt; &#123;</span><br><span class="line">        <span class="keyword">if</span> (props.<span class="property">options</span>.<span class="property">stream</span>) &#123;</span><br><span class="line">            <span class="comment">// ...</span></span><br><span class="line"></span><br><span class="line">            <span class="keyword">return</span> <span class="title class_">Stream</span>.<span class="title function_">fromSSEResponse</span>(</span><br><span class="line">                response,</span><br><span class="line">                props.<span class="property">controller</span>,</span><br><span class="line">                client,</span><br><span class="line">                props.<span class="property">options</span>.<span class="property">__synthesizeEventData</span>,</span><br><span class="line">            ) <span class="keyword">as</span> <span class="built_in">any</span>;</span><br><span class="line">        &#125;</span><br></pre></td></tr></table></figure></div><ul><li>这里，当 <code>props.options.stream</code> 设为 true 的时候，返回的是 <code>Stream.fromSSEResponse</code> 函数的返回值，具体来说：</li></ul><div class="code-container" data-rel="Ts"><figure class="iseeu highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Stream</span> &#123;</span><br><span class="line">    <span class="title function_">constructor</span>(<span class="params">iterator, controller, client</span>) &#123;</span><br><span class="line">        <span class="variable language_">this</span>.<span class="property">iterator</span> = iterator;</span><br><span class="line">        <span class="comment">// ...</span></span><br><span class="line">    &#125;</span><br><span class="line">    <span class="keyword">static</span> <span class="title function_">fromSSEResponse</span>(<span class="params">response, controller, client, synthesizeEventData</span>) &#123;</span><br><span class="line">        <span class="keyword">let</span> consumed = <span class="literal">false</span>;</span><br><span class="line">        <span class="keyword">const</span> logger = client ? (<span class="number">0</span>, log_1.<span class="property">loggerFor</span>)(client) : <span class="variable language_">console</span>;</span><br><span class="line">        <span class="keyword">async</span> <span class="keyword">function</span>* <span class="title function_">iterator</span>(<span class="params"></span>) &#123;</span><br><span class="line">            <span class="comment">// iterator implementation</span></span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">new</span> <span class="title class_">Stream</span>(iterator, controller, client);</span><br><span class="line">    &#125;</span><br></pre></td></tr></table></figure></div><ul><li>可以看到，这就是最开始我们所寻找的 iterator。（ <em>具体的 iterator 实现内部过于复杂，懒得看</em> ）</li></ul><hr><ul><li><p>既然已经读完了（部分）源码，是时候问出那个问题了：<del>Why so serious?</del> 为什么要搞的这么复杂？</p></li><li><p>说实话，面对这么一大段像意大利面一样（或许不至于）的源码，我是没什么头绪的。我明白知其然知其所以然的道理，但确实个人能力有限，没有开发过这种级别的，涉及 streaming 的库，难以理解为什么需要如此复杂的处理流程。</p></li><li><p>因此我询问了 ChatGPT，得到如下回复：</p><blockquote><p>Yes — it is more complicated than a one-off app would need, but it is solving library-level problems.</p><ul><li>One API must handle both normal JSON responses and streamed SSE responses.</li><li>It must work in Node, browsers, edge runtimes, and React Native.</li><li>It must preserve await client.chat.completions.create(…) ergonomics while still returning an async stream when requested.</li><li>It must handle aborts, retries, parse errors, and request metadata consistently.</li><li>It reuses the same streaming engine for chat, responses, assistants, and other endpoints.</li></ul></blockquote><blockquote><p>The layering is intentional:</p><ul><li><code>APIPromise</code> delays parsing until the user actually awaits&#x2F;then-chains.</li><li><code>defaultParseResponse()</code> decides whether the result is JSON or a Stream.</li><li><code>Stream.fromSSEResponse()</code> converts the fetch ReadableStream into SSE events.</li><li><code>ReadableStreamToAsyncIterable(...)</code> is just the adapter that makes browser&#x2F;fetch streams usable with for await.</li></ul></blockquote><blockquote><p>So the complexity is mostly an adapter stack:</p><ul><li>HTTP response -&gt; ReadableStream -&gt; async iterable -&gt; SSE frames -&gt; parsed chunks -&gt; user loop.</li></ul></blockquote><blockquote><p>For a library, that’s useful because:</p><ul><li>it keeps the public API small,</li><li>it hides runtime differences,</li><li>and it gives consistent cancellation&#x2F;error behavior.</li></ul></blockquote><blockquote><p>So: yes, the internal call graph is twisty, but it’s mostly “plumbing” to make streaming feel simple at the call site.</p></blockquote></li><li><p>看起来，要求一个库在多种环境，面对多种输入和输出，以及存在异步资源请求的情况下工作并不是一件容易的事情。</p></li></ul></div></details><ul><li>说实话，感觉这种 <a class="link"   href="https://www.stainless.com/" >Stainless API<i class="fa-solid fa-arrow-up-right ml-[0.2em] font-light align-text-top text-[0.7em] link-icon"></i></a> 生成的 SDK 代码读起来好像并不是那么有价值。</li></ul><h1 id="题外话"><a href="#题外话" class="headerlink" title="题外话"></a>题外话</h1><ul><li><p>说点题外话——我感觉开发&#x2F;学习这个网页插件的方式和传统的方式大有不同。具体来说，我已经变的非常依赖 LLM 了。</p></li><li><p>尽管我自称所谓“轻度维新派”，坚持自己写代码，对于 LLM 生成的代码则每一行都自己审计并尝试理解，改正其错误或者根据 codebase 变化进行魔改。但不可否认的是，我对于许多开发过程中所需要的知识都是通过 LLM 获得的，我发现自己变的非常依赖 LLM 提供的 SOTA 技术选型&#x2F;起步框架，辅助阅读源码的能力以及设计模式方面的建议。</p></li><li><p>倒也不是感到恐慌，能够普及的新技术自有其优势和用处，若是要依靠传统的教程 + 搜索引擎 + 论坛 + 官方技术文档的方式写插件，那怕是费老鼻子劲了。</p></li><li><p>但确实，这让我再次对于 LLM 介入开发的程度感觉到一种模糊的摇摆之感——在“古法编码”和“vibe coding”之间的平衡点，到底在哪里呢？</p></li><li><p>倘若将来这些大型科技公司因为“没有故事可讲”，“没有对得起估值的盈利方式”而无法再提供这样的 API 服务，又或者是让价格大幅上涨，限额进一步缩减，我还能以同等的效率学习&#x2F;写出同等质量的代码吗？</p></li></ul>]]>
    </content>
    <id>https://blog.imlast.top/2026/04/01/scribecx-2/</id>
    <link href="https://blog.imlast.top/2026/04/01/scribecx-2/"/>
    <published>2026-04-01T08:39:00.000Z</published>
    <summary>LLM 对话请求周期，对话的持久化存储，以及流式传输的实现</summary>
    <title>Scribe.cx 2 - 对话与持久化存储</title>
    <updated>2026-04-01T08:39:00.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>Last</name>
    </author>
    <category term="工具使用" scheme="https://blog.imlast.top/categories/%E5%B7%A5%E5%85%B7%E4%BD%BF%E7%94%A8/"/>
    <category term="wayland" scheme="https://blog.imlast.top/tags/wayland/"/>
    <category term="python" scheme="https://blog.imlast.top/tags/python/"/>
    <category term="linuxqq" scheme="https://blog.imlast.top/tags/linuxqq/"/>
    <category term="shell" scheme="https://blog.imlast.top/tags/shell/"/>
    <content>
      <![CDATA[<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><ul><li><p>前些时日笔者就注意到 linuxqq 中复制内容实际上输出到了 X11 剪贴板中而非 Wayland，尽管 linuxqq 窗口确实是在 Wayland 混成器下运行的。推测可能是一些旧版本的 Electron 接口未能及时更新所致。</p></li><li><p>那时候，笔者所在群聊的群主用 AI 写了一个同步脚本，来将 x11 剪贴板中的内容同步到 Wayland 粘贴板。详见 <a href="https://blog.imlast.top/2025/10/15/linuxqq-clipboard-issue/">这篇文章</a>。</p></li></ul><h2 id="两个悬而未决的问题：锁丢失和内容截断"><a href="#两个悬而未决的问题：锁丢失和内容截断" class="headerlink" title="两个悬而未决的问题：锁丢失和内容截断"></a>两个悬而未决的问题：锁丢失和内容截断</h2><ul><li>在使用过程中，笔者发现该脚本出现了以下两个问题：<ol><li>休眠（Hibernate）后，锁文件丢失导致多个脚本实例同时运行</li><li>体积较大的图片文件在会被“截断”，具体来说，粘贴出的图片数据不完整，通常表现为只有截图区域的上半部分</li></ol></li></ul><hr><ul><li><p>关于第一个问题，我一开始 <strong>推测</strong> 是放置锁的位置不对：<code>/tmp/clipboard_sync.lock</code>。原先猜想会不会是 Hibernate 清空了 <code>/tmp</code> 路径下的文件，但写了一个简单的脚本测试之后，发现并不会发生这种事情。后续我修改了脚本，将锁文件存放在 <code>$XDG_RUNTIME_DIR/</code> 路径下，至于是否会发生同样的问题，尚未进行充分的测试。</p></li><li><p>于是这个问题就变成了一个未解之谜……</p></li><li><p>就第二个问题来说，<strong>推测</strong> 是 shell 语言中的管道操作异步执行导致的，关键在于脚本的这一部分：</p>  <div class="code-container" data-rel="Bash"><figure class="iseeu highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># Pipe the image data directly to the Wayland clipboard</span></span><br><span class="line">xclip -selection clipboard -t <span class="string">&quot;<span class="variable">$x11_img_type</span>&quot;</span> -o 2&gt;/dev/null | wl-copy -t <span class="string">&quot;<span class="variable">$x11_img_type</span>&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># ...</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># Clear the X11 clipboard to make Wayland the single source of truth and prevent echo-syncing</span></span><br><span class="line">xclip -selection clipboard -i /dev/null</span><br></pre></td></tr></table></figure></div></li><li><p>可以看到，在通过管道命令输出 Wayland 剪贴板的内容到 X11 剪贴板后，脚本几乎是立刻（中间还有一些设置变量的步骤）清空了 X11 剪贴板。如果此时管道传输没有结束，就会导致同步数据被截断的情况。</p></li><li><p>然后事后不论是直接测试这段逻辑，当时反复运行脚本，甚至手动删除锁文件运行多个脚本实例，都没有观察到类似的 bug 再次出现。</p></li><li><p>所以这也成了一个未解之谜……</p></li></ul><h2 id="重复同步"><a href="#重复同步" class="headerlink" title="重复同步"></a>重复同步</h2><ul><li><p>在我尝试使用 Python 编写一个相同逻辑的脚本之后，观察 log 发现：</p>  <div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">[15:58:10] ✓ WL -&gt; X11 | Text:</span><br><span class="line">xclip 同步到 wl-clipboard 之后 xclip 被清空了，但是 wl-paste -...</span><br><span class="line">[15:58:11] ✓ X11 -&gt; WL | Text:</span><br><span class="line">xclip 同步到 wl-clipboard 之后 xclip 被清空了，但是 wl-paste -...</span><br><span class="line">[15:58:19] ✓ WL -&gt; X11 | Text:</span><br><span class="line">我之前看到同步两次的情况加了个锁避免写回，结果发现 x11 被清空了，然后又触发同步，wl-clip...</span><br><span class="line">[15:58:20] ✓ X11 -&gt; WL | Text:</span><br><span class="line">我之前看到同步两次的情况加了个锁避免写回，结果发现 x11 被清空了，然后又触发同步，wl-clip...</span><br><span class="line">[15:58:21] ✓ WL -&gt; X11 | Text:</span><br><span class="line">非常搞笑</span><br><span class="line">[15:58:22] ✓ X11 -&gt; WL | Text:</span><br><span class="line">非常搞笑</span><br></pre></td></tr></table></figure></div></li><li><p>可以看到，每次复制内容，脚本都执行了两次同步行为。脚本内监听 Wayland 的函数是这样的：</p>  <div class="code-container" data-rel="Python"><figure class="iseeu highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">def</span> <span class="title function_">watch_wayland</span>():</span><br><span class="line">    <span class="string">&quot;&quot;&quot;Listener process: wl-paste --watch blocks the process&quot;&quot;&quot;</span></span><br><span class="line"></span><br><span class="line">    <span class="comment"># wl-paste will echo &#x27;1&#x27; whenever the clipboard changes</span></span><br><span class="line">    proc = subprocess.Popen(</span><br><span class="line">        [<span class="string">&quot;wl-paste&quot;</span>, <span class="string">&quot;--watch&quot;</span>, <span class="string">&quot;echo&quot;</span>, <span class="string">&quot;1&quot;</span>], stdout=subprocess.PIPE</span><br><span class="line">    )</span><br><span class="line">    <span class="keyword">while</span> <span class="literal">True</span>:</span><br><span class="line">        <span class="keyword">if</span> proc.stdout.readline():  <span class="comment"># blocks until clipboard changes</span></span><br><span class="line">            time.sleep(<span class="number">0.05</span>)</span><br><span class="line">            sync_wayland_to_x11()</span><br></pre></td></tr></table></figure></div></li><li><p>不难想到，x11 -&gt; wayland 的脚本同步行为也触发了 <code>wl-paste --watch echo 1</code> 的监听机制，从而使得已经同步到 Wayland 的内容被同步回了 X11。</p></li><li><p>于是我理所当然的想着加一个变量充当锁机制，在 x11 -&gt; wayland 同步行为发生后让 <code>watch_wayland</code> 跳过下一次监听机制的触发：</p>  <div class="code-container" data-rel="Python"><figure class="iseeu highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">def</span> <span class="title function_">sync_wayland_to_x11</span>():</span><br><span class="line">    <span class="string">&quot;&quot;&quot;Wayland (event driven) -&gt; X11&quot;&quot;&quot;</span></span><br><span class="line">    <span class="keyword">global</span> last_text, last_img_hash, wl_ignore_next_update</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> wl_ignore_next_update:</span><br><span class="line">        wl_ignore_next_update = <span class="literal">False</span></span><br><span class="line">        <span class="keyword">return</span></span><br><span class="line"></span><br><span class="line">    <span class="comment"># ...</span></span><br></pre></td></tr></table></figure></div><p>  <em>(在运行 <code>wl-copy</code> 命令之前将 <code>wl_ignore_next_update</code> 设为 True)</em></p></li><li><p>却发现从 linuxqq 中复制内容后剪贴板中什么都没有，何意味？</p></li><li><p>查看 log：</p>  <div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">[16:27:03] ✓ X11 -&gt; WL | Text:</span><br><span class="line">xclip 同步到 wl-clipboard 之后 xclip 被清空了，但是 wl-paste -...</span><br><span class="line">[16:27:04] ✓ X11 -&gt; WL | Text:</span><br><span class="line"></span><br></pre></td></tr></table></figure></div></li><li><p>可以看到，日志最后的同步信息是一个 <strong>空行</strong>，意味着最后 Wayland 剪贴板被同步了成了“空”，导致粘贴行为什么都没有输出。</p></li><li><p>然后我使用 pdb 一步一步的执行脚本，并在旁边打开一个终端窗口时刻观察 X11 剪贴板中的内容变化，最终发现运行 <code>wl-copy</code> 同步 X11 剪贴板中的内容至 Wayland 剪贴板之后，X11 剪贴板中的内容被清空了。</p></li><li><p>这是为什么？</p></li></ul><h2 id="剪贴板所有权"><a href="#剪贴板所有权" class="headerlink" title="剪贴板所有权"></a>剪贴板所有权</h2><ul><li><p>原因在于系统剪贴板具有一个 <code>ownership</code> 的属性，这一点在 Wayland 和 XServer 上是一样的。</p></li><li><p>当脚本运行 <code>wl-copy</code> 来同步内容时，CLIPBOARD 这个 selection 的 ownership 就不归属于 linuxqq（准确的来说是 linuxqq 内部的某个陈旧的子模块）所有了。</p></li><li><p>事实上，笔者先前对于系统剪贴板的运作有一个误解——<strong>误以为剪贴板是一个归属于 Wayland&#x2F;XServer 所管理的 Buffer</strong>，然而事实上在 Wayland&#x2F;XServer 中复制粘贴这样的跨窗口数据通信，事实上是一种“拉取式”的行为，即当“粘贴”操作发生时，当前窗口会向剪贴板的 owner 发送一个请求获取 CLIPBOARD 中的内容。</p></li><li><p>这样一来，就不难理解为什么一旦运行 <code>wl-copy</code> 后，剪贴板中的内容就直接“消失不见了”——因为 ownership 已经不归属于 linuxqq，而是属于 Wayland Compositor。而同步脚本自身并没有 XWayland 那样同步剪贴板的行为，<code>xclip</code> 自然也无从获取数据。</p></li></ul><hr><ul><li><p>对于这个问题，我能想到三种解决方法：</p><ol><li>回到原先没有同步锁的逻辑， <strong>任由第二次同步发生</strong>（事实上是让该脚本始终掌握剪贴板的 ownership）</li><li>保留同步锁，但是“双写”，即每次同步都 <strong>同时写入 Wayland 和 X11 的剪贴板</strong></li><li>跳过空内容的同步，即 <strong>在 linuxqq 失去剪贴板所有权后，检测到 xclip 输出内容为空（有更新）则跳过同步</strong></li></ol></li><li><p>对于 <code>1</code>，说实话这有点 “该系统依赖 bug 运行” 的味道，按照正常的逻辑 X11 同步到 Wayland 后不应该再触发一次 Wayland 同步回 X11 的行为，嗯，正常来说是这样的。</p></li><li><p>对于 <code>2</code>，这是最符合逻辑的做法，代价就是实现起来比较复杂，并且性能上也有所权衡。</p></li><li><p>对于 <code>3</code> 这其实是基于 <em>在 linuxqq 中的“粘贴”行为读取的是 Wayland 剪贴板而非 X11</em> 这个事实，才能起作用的一种方式。其实在我看来这种行为挺奇怪的，尤其是对于一个“同步”脚本来说。</p></li><li><p>但从结果来说，这三种方案中的任意一个都能解决问题，选择哪个其实无所谓。</p></li></ul><h2 id="clipsync"><a href="#clipsync" class="headerlink" title="clipsync"></a>clipsync</h2><ul><li><p>笔者在研究的过程中，正巧 GitHub 上一位用户名为 <a class="link"   href="https://github.com/SHORiN-KiWATA" >SHORiN-KiWATA<i class="fa-solid fa-arrow-up-right ml-[0.2em] font-light align-text-top text-[0.7em] link-icon"></i></a> 发布了他的<a class="link"   href="https://github.com/SHORiN-KiWATA/clipsync/tree/main" >剪贴板同步脚本<i class="fa-solid fa-arrow-up-right ml-[0.2em] font-light align-text-top text-[0.7em] link-icon"></i></a>，使用 Shellscript 编写。</p></li><li><p>笔者下载研读了一下，发现他所使用的其实就是上述第一种方法，并且在 md5 哈希计算比对的时候加入了空值检测。</p></li><li><p>观察其 log 可以得到佐证：</p>  <div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line">[2026-02-14 01:45:38] [INFO] &gt;&gt;&gt; 监测到 X11 剪贴板变化信号</span><br><span class="line">[2026-02-14 01:45:38] [DEBUG] X11 包含的数据类型: [UTF8_STRING]</span><br><span class="line">[2026-02-14 01:45:38] [INFO] 匹配类型: 纯文字 (UTF8_STRING)</span><br><span class="line">[2026-02-14 01:45:38] [DEBUG] 哈希比对: Wayland[8beccf70cb34cb30fcf1b84008cc6d7c] vs X11[221361ceefda8a8097851867a83e2dc1]</span><br><span class="line">[2026-02-14 01:45:38] [ACTION] 内容不同，正在同步到 Wayland...</span><br><span class="line">[2026-02-14 01:45:38] [SUCCESS] 纯文字同步成功</span><br><span class="line">[2026-02-14 01:45:38] [INFO] === 脚本触发: 开始检测剪贴板数据类型 ===</span><br><span class="line">[2026-02-14 01:45:38] [DEBUG] 检测到的源类型列表: [UTF8_STRING,text/plain,text/plain;charset=utf-8,TEXT,STRING,UTF8_STRING]</span><br><span class="line">[2026-02-14 01:45:38] [INFO] 匹配类型: 纯文字 (text/plain)</span><br><span class="line">[2026-02-14 01:45:38] [ACTION] 正在同步纯文字到 X11...</span><br><span class="line">[2026-02-14 01:45:38] [SUCCESS] 纯文字同步完成</span><br><span class="line">[2026-02-14 01:45:38] [INFO] --- 流程结束 ---</span><br><span class="line">[2026-02-14 01:45:38] [INFO] &gt;&gt;&gt; 监测到 X11 剪贴板变化信号</span><br><span class="line">[2026-02-14 01:45:38] [DEBUG] X11 包含的数据类型: [TARGETS,UTF8_STRING]</span><br><span class="line">[2026-02-14 01:45:38] [INFO] 匹配类型: 纯文字 (UTF8_STRING)</span><br><span class="line">[2026-02-14 01:45:38] [DEBUG] 哈希比对: Wayland[221361ceefda8a8097851867a83e2dc1] vs X11[221361ceefda8a8097851867a83e2dc1]</span><br><span class="line">[2026-02-14 01:45:38] [SKIP] 内容一致，跳过同步</span><br><span class="line">[2026-02-14 01:45:38] [INFO] --- 本次循环结束，等待下一次 clipnotify 信号 ---</span><br></pre></td></tr></table></figure></div></li><li><p>可以看到，一次 X11 剪贴板变化确实触发了 X11 -&gt; Wayland &amp; Wayland -&gt; X11 两次同步，这确实有效的解决了上述问题。</p></li><li><p>同时，使用 systemd 来管理脚本的启停，相较于让脚本自己持有锁来说是一种更好的方式。</p></li><li><p>本着不重复造轮子 <del>（懒惰）</del> 的原则，笔者决定采用此脚本。</p>  <div class="code-container" data-rel="Bash"><figure class="iseeu highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">paru -S clipsync-git</span><br><span class="line">systemd --user <span class="built_in">enable</span> clipsync.service --now</span><br></pre></td></tr></table></figure></div></li></ul><h2 id="另一种方式：XWayland-模式启动"><a href="#另一种方式：XWayland-模式启动" class="headerlink" title="另一种方式：XWayland 模式启动"></a>另一种方式：XWayland 模式启动</h2><ul><li><p>既然是由于 linuxqq 对 Wayland 的“表面适配”导致的此等问题，那么直接让 linuxqq 运行在 XWayland 模式下，利用 XWayland 自带的同步机制不就好了？</p><ul><li><p>在 <code>~/.config</code> 下创建 <code>qq-electron-flags.conf</code>，内容如下：</p>  <div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">--ozone-platform-hint=x11</span><br></pre></td></tr></table></figure></div></li></ul></li><li><p>经测试，XWayland 模式下运行的 linuxqq 没有剪贴板同步问题。</p></li><li><p>或许这才是最简便高效的方式。（摊手）</p></li></ul><h2 id="尾声：没有商业价值的用户"><a href="#尾声：没有商业价值的用户" class="headerlink" title="尾声：没有商业价值的用户"></a>尾声：没有商业价值的用户</h2><ul><li><p>虽说现如今的 linuxqq 相较于 QQ 的 Electron 时代之前的那个老古董已经完善了不少，但还是在许多细节之处显得非常“敷衍”——不止是剪贴板，语音消息 &amp; 屏幕共享在 Linux 上依旧没有支持。</p><blockquote><p>感觉腾讯全系对桌面 gnu&#x2F;linux 都是这个态度（<br>尤其是腾讯会议在 Ubuntu 下二维码都加载不出来，虚拟摄像头也读不到:sweat_smile:</p></blockquote></li><li><p>该怎么说呢，或许腾讯非常明白他旗下软件的绝大部分用户都不会使用 Linux 作为操作系统，因而压根没投入多少资源。</p></li><li><p>果然 Linux 用户对于国内这些软件厂商来说纯粹是边缘群体，是“没有商业价值的用户”，令人感慨。</p></li></ul><h2 id="附录：-关于-linuxqq-的-X11-剪贴板调用"><a href="#附录：-关于-linuxqq-的-X11-剪贴板调用" class="headerlink" title="附录： 关于 linuxqq 的 X11 剪贴板调用"></a>附录： 关于 linuxqq 的 X11 剪贴板调用</h2><ul><li><p>使用如下命令启动 linuxqq：</p>  <div class="code-container" data-rel="Bash"><figure class="iseeu highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">env</span> -u DISPLAY qq</span><br></pre></td></tr></table></figure></div></li><li><p>linuxqq 正常启动，但只要在 linuxqq 中右键复制，或者按下 <C-c>，就会发生崩溃报错：</p>  <div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line">[BuglyService.cpp][handleSignal][382]Stack is succesfully dumped by libUnwind.</span><br><span class="line">[BuglyService.cpp][handleSignal][384]Native stack:</span><br><span class="line">$$00    pc 00000000059d41af    /opt/QQ/resources/app/wrapper.node [x86_64::ae125ff74fb6eafb0000000000000000]</span><br><span class="line">$$01    pc 000000000009698b    /usr/lib/libc.so.6 [x86_64::0f8de86da4eead11213c59d926ca3796]</span><br><span class="line">$$02    pc 000000000011aa0c    /usr/lib/libc.so.6 [x86_64::0f8de86da4eead11213c59d926ca3796]</span><br><span class="line"></span><br><span class="line">[BuglyService.cpp][handleSignal][386]Record map file of thread: 787</span><br><span class="line">[BuglyService.cpp][handleSignal][396]Dumping of native stack finished.</span><br><span class="line">235,0001,Record EupInfo</span><br><span class="line">235,0000,write key fail</span><br><span class="line">235,0001,write key fail</span><br><span class="line">235,0002,write key fail</span><br><span class="line">235,0003,write key fail</span><br><span class="line">235,0004,write key fail</span><br><span class="line">235,0005,write key fail</span><br><span class="line">235,0006,write key fail</span><br><span class="line">235,0007,write key fail</span><br><span class="line">235,0002,Failed to record java thread name.</span><br><span class="line">235,0008,write key fail</span><br><span class="line">235,0009,write key fail</span><br><span class="line">235,0010,write key fail</span><br><span class="line">235,0003,EupInfo has been recorded.</span><br><span class="line">235,0004,Record native key-value list.</span><br><span class="line">235,0005,Native key-value list has been recorded.</span><br><span class="line">235,0006,Record native log.</span><br><span class="line">235,0000,Native log has not been initiated.</span><br><span class="line">235,0007,Native log has been recorded.</span><br><span class="line">[BuglyService.cpp][clearEupInfo][283]Clear eupInfo object.</span><br><span class="line">235,0005,Try to unlock file: /home/last/.config/QQ/crash_files//../files/native_record_lock</span><br><span class="line">235,0006,Successfully unlock file: /home/last/.config/QQ/crash_files//../files/native_record_lock</span><br><span class="line">[BuglyService.cpp][handleSignal][441]Restored signal handlers.</span><br></pre></td></tr></table></figure></div></li></ul><h2 id="参考资料"><a href="#参考资料" class="headerlink" title="参考资料"></a>参考资料</h2><ul><li><p><a class="link"   href="https://www.x.org/releases/current/doc/xorg-docs/icccm/icccm.html#Peer_to_Peer_Communication_by_Means_of_Selections" >X Consortium Standard: Chapter 2 Peer-to-Peer Communication by Means of Selections<i class="fa-solid fa-arrow-up-right ml-[0.2em] font-light align-text-top text-[0.7em] link-icon"></i></a></p></li><li><p><a class="link"   href="https://en.wikipedia.org/wiki/X_Window_System_selection#Clipboard" >Wikipedia: X Window System selection<i class="fa-solid fa-arrow-up-right ml-[0.2em] font-light align-text-top text-[0.7em] link-icon"></i></a></p></li></ul>]]>
    </content>
    <id>https://blog.imlast.top/2026/02/13/clipboard-sync/</id>
    <link href="https://blog.imlast.top/2026/02/13/clipboard-sync/"/>
    <published>2026-02-13T18:56:36.000Z</published>
    <summary>关于 linuxqq 的剪贴板操作行为，以及 Linux 下 X11/Wayland 的剪贴板同步行为</summary>
    <title>关于 X11 与 Wayland 的剪贴板同步问题</title>
    <updated>2026-02-13T18:56:36.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>Last</name>
    </author>
    <category term="笔记" scheme="https://blog.imlast.top/categories/%E7%AC%94%E8%AE%B0/"/>
    <category term="browser" scheme="https://blog.imlast.top/tags/browser/"/>
    <category term="wxt" scheme="https://blog.imlast.top/tags/wxt/"/>
    <content>
      <![CDATA[<h2 id="webextension-polyfill"><a href="#webextension-polyfill" class="headerlink" title="webextension-polyfill"></a>webextension-polyfill</h2><ul><li><p>查阅 wxt 相关 <a class="link"   href="https://wxt.dev/guide/essentials/extension-apis.html#using-webextension-polyfill" >README<i class="fa-solid fa-arrow-up-right ml-[0.2em] font-light align-text-top text-[0.7em] link-icon"></i></a></p>  <div class="code-container" data-rel="Bash"><figure class="iseeu highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">pnpm i @wxt-dev/webextension-polyfill webextension-polyfill</span><br></pre></td></tr></table></figure></div></li><li><p>以及在 <code>wxt.config.ts</code> 中加入：</p>  <div class="code-container" data-rel="Ts"><figure class="iseeu highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="title function_">defineConfig</span>(&#123;</span><br><span class="line">    <span class="comment">//...</span></span><br><span class="line">    <span class="attr">modules</span>: [<span class="string">&quot;@wxt-dev/webextension-polyfill&quot;</span>]</span><br><span class="line">    <span class="comment">//...</span></span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure></div></li><li><p>此外，还需要 <code>@types/webextension-polyfill</code> 以及 <code>@types/firefox-webext-browser</code> 来扩展 <code>browser</code> 的 Firefox 成员。</p>  <div class="code-container" data-rel="Bash"><figure class="iseeu highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">pnpm i -D @types/firefox-webext-browser</span><br></pre></td></tr></table></figure></div></li></ul><h2 id="括号作为行开头"><a href="#括号作为行开头" class="headerlink" title="括号作为行开头"></a>括号作为行开头</h2><p><img src="https://s2.loli.net/2026/01/31/SrWPQwFyUzN8JLx.png" alt="semi-1.png"></p><p><img src="https://s2.loli.net/2026/01/31/n5kgCxzQI6uaVKr.png" alt="semi-2.png"></p><ul><li>这就是 JavaScript ASI 吗，给我整笑了。</li></ul><hr><h2 id="默认打开-Side-Panel"><a href="#默认打开-Side-Panel" class="headerlink" title="默认打开 Side Panel"></a>默认打开 Side Panel</h2><h3 id="Firefox"><a href="#Firefox" class="headerlink" title="Firefox"></a>Firefox</h3><ul><li><p>经测试，<code>manifest.json</code> 中的 <code>sidebar_action</code> 或者 <code>side_panel</code> 事实上没有任何作用。</p></li><li><p>也可能是 WXT 抹平了这其中的一些坑，没有细究。</p></li></ul><hr><ul><li><p>对于 Firefox，点击扩展图标触发 listener 函数的机制和 <code>default_popup</code> 是 <strong>冲突</strong> 的，这意味着如果浏览器发现 manifest 中的 <code>action</code> 一项中存在 <code>default_popup</code>，那么点击扩展图标只会打开 popup 窗口而不会触发 <code>background.ts</code> 中挂载的 listener 函数。</p></li><li><p>因此，想要默认打开侧板，需要删除 <code>entrypoints</code> 中的 <code>popup</code> 来防止 wxt 自动在 <code>manifest.json</code> 中生成 <code>default_popup</code>。</p><ul><li><p>也可以通过一种奇怪的方式来让 WXT 忽略 <code>popup</code>:</p><blockquote><p><a class="link"   href="https://github.com/wxt-dev/wxt/issues/1433" >https://github.com/wxt-dev/wxt/issues/1433<i class="fa-solid fa-arrow-up-right ml-[0.2em] font-light align-text-top text-[0.7em] link-icon"></i></a></p></blockquote></li></ul></li><li><p>但是不能完全删去 <code>action</code>，因为这样一来<code>background.ts</code> 就无法访问 <code>browser.browserAction</code> 或者 <code>browser.action</code>。</p><blockquote><p><a class="link"   href="https://github.com/wxt-dev/wxt/issues/669" >https://github.com/wxt-dev/wxt/issues/669<i class="fa-solid fa-arrow-up-right ml-[0.2em] font-light align-text-top text-[0.7em] link-icon"></i></a><br><a class="link"   href="https://wxt.dev/guide/essentials/config/manifest.html#action-without-popup" >https://wxt.dev/guide/essentials/config/manifest.html#action-without-popup<i class="fa-solid fa-arrow-up-right ml-[0.2em] font-light align-text-top text-[0.7em] link-icon"></i></a></p></blockquote><ul><li><p>可以在 <code>wxt.config.ts</code> 中的 <code>manifest</code> 部分添加一个空的 <code>action</code>，如下所示：</p>  <div class="code-container" data-rel="Ts"><figure class="iseeu highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="title function_">defineConfig</span>(&#123;</span><br><span class="line">    <span class="comment">// ...</span></span><br><span class="line">    <span class="attr">manifest</span>: &#123;</span><br><span class="line">        <span class="comment">// ...</span></span><br><span class="line">        <span class="attr">action</span>: &#123;&#125;</span><br><span class="line">        <span class="comment">// ...</span></span><br><span class="line">    &#125;</span><br><span class="line">    <span class="comment">// ...</span></span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure></div></li></ul></li><li><p>然后在 <code>background.ts</code> 中注册监听函数：</p>  <div class="code-container" data-rel="Ts"><figure class="iseeu highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">if</span> (<span class="keyword">import</span>.<span class="property">meta</span>.<span class="property">env</span>.<span class="property">MANIFEST_VERSION</span> === <span class="number">3</span>) &#123;</span><br><span class="line">    browser.<span class="property">action</span>.<span class="property">onClicked</span>.<span class="title function_">addListener</span>(sidePanelManager.<span class="property">open</span>);</span><br><span class="line">&#125; <span class="keyword">else</span> &#123;</span><br><span class="line">    browser.<span class="property">browserAction</span>.<span class="property">onClicked</span>.<span class="title function_">addListener</span>(sidePanelManager.<span class="property">open</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div></li></ul><h3 id="Chrome"><a href="#Chrome" class="headerlink" title="Chrome"></a>Chrome</h3><ul><li><p>同样的，需要 <code>manifest.json</code> 中包含 <code>action</code>，且需要删除 <code>popup</code>：</p><blockquote><p><a class="link"   href="https://developer.chrome.com/docs/extensions/reference/api/sidePanel#open-action-icon" >https://developer.chrome.com/docs/extensions/reference/api/sidePanel#open-action-icon<i class="fa-solid fa-arrow-up-right ml-[0.2em] font-light align-text-top text-[0.7em] link-icon"></i></a></p></blockquote></li><li><p>经测试，当 <code>PanelBehavior</code> 中的 <code>openPanelOnActionClick</code> 被设为 <code>true</code> 的时候，点击扩展图标同样不会触发 <code>background.ts</code> 中挂载的 listener 函数。</p></li><li><p><code>openPanelOnActionClick</code> 默认设置为 <code>false</code>，保险起见再手动设置一遍：</p>  <div class="code-container" data-rel="Ts"><figure class="iseeu highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// Chrome specific: Disable the default declarative behavior to ensure</span></span><br><span class="line"><span class="comment">// the onClicked event is dispatched to our listener.</span></span><br><span class="line"><span class="keyword">if</span> (<span class="keyword">import</span>.<span class="property">meta</span>.<span class="property">env</span>.<span class="property">CHROME</span> &amp;&amp; <span class="keyword">typeof</span> chrome.<span class="property">sidePanel</span>?.<span class="property">setPanelBehavior</span> === <span class="string">&quot;function&quot;</span>) &#123;</span><br><span class="line">    chrome.<span class="property">sidePanel</span>.<span class="title function_">setPanelBehavior</span>(&#123; <span class="attr">openPanelOnActionClick</span>: <span class="literal">false</span> &#125;).<span class="title function_">catch</span>(<span class="function">(<span class="params">error</span>) =&gt;</span> &#123;</span><br><span class="line">        <span class="variable language_">console</span>.<span class="title function_">error</span>(<span class="string">&quot;[Chrome] Error resetting panel behavior: &quot;</span>, error);</span><br><span class="line">    &#125;);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div></li></ul><h3 id="完整代码"><a href="#完整代码" class="headerlink" title="完整代码"></a>完整代码</h3><details class="relative my-4 border border-border-color bg-second-background-color rounded-md  purple" data-header-exclude><summary class="px-4 py-2 rounded-md shadow-[0_0_2px_0_var(--shadow-color-1)] cursor-pointer not-markdown"><i class="fa-solid fa-chevron-right"></i>@/utils/sidepanel.ts</summary><div class="content p-4 "><div class="code-container" data-rel="Ts"><figure class="iseeu highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; <span class="title class_">Tabs</span> &#125; <span class="keyword">from</span> <span class="string">&quot;webextension-polyfill&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Defines the contract for side panel operations across different browsers.</span></span><br><span class="line"><span class="comment"> * This abstraction allows the rest of the extension to trigger UI components</span></span><br><span class="line"><span class="comment"> * without platform-specific branching.</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">interface</span> <span class="title class_">SidePanelManager</span> &#123;</span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * Toggles or opens the side panel depending on the browser&#x27;s capabilities.</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> tab The current active tab.</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="attr">open</span>: <span class="function">(<span class="params"><span class="attr">tab</span>: <span class="title class_">Tabs</span>.<span class="title class_">Tab</span></span>) =&gt;</span> <span class="title class_">Promise</span>&lt;<span class="built_in">void</span>&gt;;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Chrome implementation using the MV3 sidePanel API.</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@requires</span> &quot;sidePanel&quot; permission in manifest.json</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">async</span> <span class="keyword">function</span> <span class="title function_">ChromeOpenSidePanel</span>(<span class="params"><span class="attr">tab</span>: <span class="title class_">Tabs</span>.<span class="title class_">Tab</span></span>) &#123;</span><br><span class="line">    <span class="comment">// Ensure the API exists</span></span><br><span class="line">    <span class="keyword">if</span> (<span class="keyword">typeof</span> chrome.<span class="property">sidePanel</span>?.<span class="property">open</span> !== <span class="string">&quot;function&quot;</span>) &#123;</span><br><span class="line">        <span class="variable language_">console</span>.<span class="title function_">error</span>(</span><br><span class="line">            <span class="string">&quot;[Chrome] sidePanel.open is not available. Check &#x27;sidePanel&#x27; permission and ensure Chrome version &gt;= 116&quot;</span></span><br><span class="line">        );</span><br><span class="line">        <span class="keyword">return</span>;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// Ensure a valid `windowId`</span></span><br><span class="line">    <span class="keyword">const</span> windowId = tab.<span class="property">windowId</span> ?? chrome.<span class="property">windows</span>.<span class="property">WINDOW_ID_CURRENT</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">        <span class="keyword">await</span> chrome.<span class="property">sidePanel</span>.<span class="title function_">open</span>(&#123; <span class="attr">windowId</span>: tab.<span class="property">windowId</span> &#125;);</span><br><span class="line">        <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&quot;[Chrome] Side panel opened via action click.&quot;</span>);</span><br><span class="line">    &#125; <span class="keyword">catch</span> (error) &#123;</span><br><span class="line">        <span class="variable language_">console</span>.<span class="title function_">error</span>(<span class="string">&quot;[Chrome] Failed to open side panel: &quot;</span>, error);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Firefox implementation using the sidebarAction API.</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@requires</span> &quot;sidebar_action&quot; definition in manifest.json</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">async</span> <span class="keyword">function</span> <span class="title function_">FirefoxOpenSidePanel</span>(<span class="params"><span class="attr">tab</span>: <span class="title class_">Tabs</span>.<span class="title class_">Tab</span></span>) &#123;</span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">        <span class="keyword">await</span> browser.<span class="property">sidebarAction</span>.<span class="title function_">open</span>();</span><br><span class="line">        <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&quot;[Firefox] Side panel opened via action click.&quot;</span>);</span><br><span class="line">    &#125; <span class="keyword">catch</span> (error) &#123;</span><br><span class="line">        <span class="variable language_">console</span>.<span class="title function_">error</span>(<span class="string">&quot;[Firefox] Failed to open side panel: &quot;</span>, error);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> <span class="attr">panelImpl</span>: <span class="title class_">Record</span>&lt;<span class="built_in">string</span>, <span class="title class_">SidePanelManager</span>&gt; = &#123;</span><br><span class="line">    <span class="attr">chrome</span>: &#123; <span class="attr">open</span>: <span class="title class_">ChromeOpenSidePanel</span> &#125;,</span><br><span class="line">    <span class="attr">firefox</span>: &#123; <span class="attr">open</span>: <span class="title class_">FirefoxOpenSidePanel</span> &#125;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * A platform-aware manager instance.</span></span><br><span class="line"><span class="comment"> * WXT optimizes this at build-time, removing the unused platform implementation.</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">const</span> <span class="attr">sidePanelManager</span>: <span class="title class_">SidePanelManager</span> =</span><br><span class="line">    panelImpl[<span class="keyword">import</span>.<span class="property">meta</span>.<span class="property">env</span>.<span class="property">BROWSER</span>] ?? panelImpl[<span class="string">&quot;chrome&quot;</span>];</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Configures the extension icon to open the side panel as the default behavior.</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@description</span></span></span><br><span class="line"><span class="comment"> * This function registers a listener to the extension&#x27;s action icon.</span></span><br><span class="line"><span class="comment"> * - **Chrome**: Triggers `chrome.sidePanel.open`. Requires `openPanelOnActionClick: false`.</span></span><br><span class="line"><span class="comment"> * - **Firefox**: Triggers `sidebarAction.open`.</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@architecture</span></span></span><br><span class="line"><span class="comment"> * Uses `browser.action` for MV3 and fallbacks to `browser.browserAction` for MV2 (Firefox).</span></span><br><span class="line"><span class="comment"> * The imperative approach is used here to allow potential pre-open side effects.</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">function</span> <span class="title function_">openSidePanelAsDefault</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="comment">// <span class="doctag">TODO:</span> When in need of multiple listener functions, define a main listener</span></span><br><span class="line">    <span class="comment">// function and call the others inside it to maintain order</span></span><br><span class="line">    <span class="comment">// <span class="doctag">FIXME:</span> async function registered as callback causing problem?</span></span><br><span class="line">    <span class="keyword">if</span> (<span class="keyword">import</span>.<span class="property">meta</span>.<span class="property">env</span>.<span class="property">MANIFEST_VERSION</span> === <span class="number">3</span>) &#123;</span><br><span class="line">        browser.<span class="property">action</span>.<span class="property">onClicked</span>.<span class="title function_">addListener</span>(sidePanelManager.<span class="property">open</span>);</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">        browser.<span class="property">browserAction</span>.<span class="property">onClicked</span>.<span class="title function_">addListener</span>(sidePanelManager.<span class="property">open</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// Chrome specific: Disable the default declarative behavior to ensure</span></span><br><span class="line">    <span class="comment">// the onClicked event is dispatched to our listener.</span></span><br><span class="line">    <span class="keyword">if</span> (<span class="keyword">import</span>.<span class="property">meta</span>.<span class="property">env</span>.<span class="property">CHROME</span> &amp;&amp; <span class="keyword">typeof</span> chrome.<span class="property">sidePanel</span>?.<span class="property">setPanelBehavior</span> === <span class="string">&quot;function&quot;</span>) &#123;</span><br><span class="line">        chrome.<span class="property">sidePanel</span>.<span class="title function_">setPanelBehavior</span>(&#123; <span class="attr">openPanelOnActionClick</span>: <span class="literal">false</span> &#125;).<span class="title function_">catch</span>(<span class="function">(<span class="params">error</span>) =&gt;</span> &#123;</span><br><span class="line">            <span class="variable language_">console</span>.<span class="title function_">error</span>(<span class="string">&quot;[Chrome] Error resetting panel behavior: &quot;</span>, error);</span><br><span class="line">        &#125;);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div></div></details>]]>
    </content>
    <id>https://blog.imlast.top/2026/01/30/scribecx-1/</id>
    <link href="https://blog.imlast.top/2026/01/30/scribecx-1/"/>
    <published>2026-01-30T09:08:26.000Z</published>
    <summary>WXT 框架下浏览器插件对 Firefox 及 chromium 系浏览器的侧边栏控制差异</summary>
    <title>Scribe.cx 1 - 跨浏览器的插件侧板控制</title>
    <updated>2026-01-30T09:08:26.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>Last</name>
    </author>
    <category term="工具使用" scheme="https://blog.imlast.top/categories/%E5%B7%A5%E5%85%B7%E4%BD%BF%E7%94%A8/"/>
    <category term="browser" scheme="https://blog.imlast.top/tags/browser/"/>
    <category term="LLM" scheme="https://blog.imlast.top/tags/LLM/"/>
    <content>
      <![CDATA[<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><ul><li><p>我想要一个浏览器中的“生成网页总结”侧板很久了。此前我一直使用的是 Brave 浏览器的 Leo，使用其免费的 Claude Haiku 模型用来生成网页总结，以及问答一些网页信息。</p></li><li><p>更换浏览器到 Zen 之后，Leo 肯定是没法用了，但是经过搜索我发现主流的两个基于 LLM 的网页总结 Harpa 和 Sider 都只支持 chromium，且都不开源。</p></li><li><p>我只好选用 Page Assist，本质上是一个用于使用本地 Ollma 模型的 Web UI。正如其窗口名称所言：</p><blockquote><p>Page Assist - A Web UI for Local AI Models</p></blockquote></li></ul><h2 id="上下文长度问题"><a href="#上下文长度问题" class="headerlink" title="上下文长度问题"></a>上下文长度问题</h2><ul><li><p>我在测试 <code>Chat with current page</code> 功能的时候发现，模型只能获取一部分的上下文。于是我把所有的 context 复制出来，发现其确实截断了网页内容。</p></li><li><p>但是我在这个插件的设置页面里找了半天也没找着任何和 <code>context window</code> 有关的设置选项，最后在 GitHub Issues 里通过搜索关键词 <code>citation</code> 找到了这样的<a class="link"   href="https://github.com/n4ze3m/page-assist/issues/513#issuecomment-3562111163" >解决方案<i class="fa-solid fa-arrow-up-right ml-[0.2em] font-light align-text-top text-[0.7em] link-icon"></i></a>：</p><blockquote><p>I just did workaround by increasing the limit of content size:</p><blockquote><p>RAG Settings -&gt; Retrieval Settings -&gt; Maximum Content Size for Full Context Mode</p></blockquote><p>Default is 7028, so I just change it to 100000 (I use mistral-large-latest in most cases).</p></blockquote></li><li><p>也是神奇，上下文长度的设置选项会放在 RAG Settings 里？这也没有用到 embedding model 和向量数据库吧。</p></li><li><p>将这个设置改为 128000 后内容截断的问题立马消失了。</p></li></ul><h2 id="自定义提示词"><a href="#自定义提示词" class="headerlink" title="自定义提示词"></a>自定义提示词</h2><ul><li><p>说实话，我对于自定义提示词并不是很熟悉。我对于那些“定义角色，划清职责，结构化输出”的提示词说实话，是非常不信任的。倒也没有什么具体的原因，就是一种感觉有种直觉上的“莫名其妙”。</p></li><li><p>话虽如此，我还是希望能声明模型在这个侧栏里的需求，因此定义了如下两个 prompt：</p><blockquote><p>分析当前网页内容。</p><ol><li>内容定性：直接指出它是深度分析、新闻快讯、营销软文还是其他类型的内容。</li><li>核心摘要：用最简短的话说明页面在讲什么，以及它的核心价值点。</li><li>定位回答：回答我的问题（如果有，没有则罗列网页内容的结构和各部分概要），并注明信息在文中的大概位置（如：开头、[XX]标题下、中部）。</li><li>诚实原则：如果内容空洞或我问的信息文中没有，请直接告知，不要润色或编造。</li></ol></blockquote></li><li><p>以及：</p><blockquote><p>作为一个技术文档提取工具，请执行以下操作：</p><ol><li>技术概览：快速列出该项目的主要功能（Features）和技术栈。</li><li>精确检索：根据我的提问，寻找特定的 API、配置参数或功能描述。</li><li>位置映射：给出该信息所在的具体章节标题或路径，以便我快速定位到原文。</li><li>无则申明：如果文档中没提到我寻找的内容，直接回复“未找到相关描述”，严禁基于常识进行推测。</li></ol><p>要求： 仅负责信息的“提取”与“定位”，保持技术术语原样，回答要干练。</p></blockquote></li><li><p>总的来说效果还可以，但是在后续关于某些具体信息提问的时候他居然还会按照这个流程来回答……哎哟，再说吧。</p></li></ul><h2 id="愿景"><a href="#愿景" class="headerlink" title="愿景"></a>愿景</h2><ul><li><p>不得不承认，当前的方案虽然有效，但属于是一种妥协。这个插件本质上并不是“通过 LLM 帮助用户快速的、更好的浏览网页”而设计的，他本质是一个本地大模型的 Web UI，那个 <code>Chat with current page</code> 更像是一个附带的额外功能。</p></li><li><p>此外，支持 Firefox 的开源插件，我在 GitHub 和 Firefox Extension Store 里搜索了一番几乎找不到几个可用的。</p></li><li><p>我在想，或许我可以自己写一个？我心中对于这样的插件的预期大概有这么几个点：</p><ol><li>总结网页，提供内容评估，标注信息来源，可以一键跳转</li><li>对于技术文档，严谨的查询 api 和文档说明，原样复述</li><li>访问文中的超链接，一并总结（可能需要限制数量）</li><li>对于懒加载内容的网页，加载过的部分予以缓存</li></ol></li><li><p>这些功能感觉并不是很难做，慢慢探索一下吧。</p></li></ul>]]>
    </content>
    <id>https://blog.imlast.top/2026/01/26/page-assist/</id>
    <link href="https://blog.imlast.top/2026/01/26/page-assist/"/>
    <published>2026-01-26T03:53:07.000Z</published>
    <summary>关于寻找“网页总结”插件的过程</summary>
    <title>Page Assist</title>
    <updated>2026-01-26T03:53:07.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>Last</name>
    </author>
    <category term="随笔" scheme="https://blog.imlast.top/categories/%E9%9A%8F%E7%AC%94/"/>
    <content>
      <![CDATA[<h2 id="毕业"><a href="#毕业" class="headerlink" title="毕业"></a>毕业</h2><ul><li><p>这一年我从大学毕业了。回首过去的四年，经历了两年疫情和 LLM 的问世，也算是感慨良多吧。</p></li><li><p>就如同我在大一时发觉的那样，在学校学习的这四年并没有给我带来很多知识的积累，对社交技能的熟络和对自我精神世界的探索倒是有所提升。</p></li><li><p>但是我没什么想在这里说的，那过去四年更多的是琐碎，迷茫和自我麻醉。我也变成了空心人，满脑稻草，犹豫不决。</p></li></ul><h2 id="考研"><a href="#考研" class="headerlink" title="考研"></a>考研</h2><ul><li><p>我选择了考研。期初我的目标是“名正言顺的再多学三年”，因为我感觉计算机和艺术领域都有太多值得我花时间进一步钻研的东西，因而不想这么快的进入社会参与工作。</p></li><li><p>事实证明，这是一个错误的选择。且不论后来我才发现的，考上硕士之后本质上和打工没什么大区别，就光是复习考研的这大半年就让我感觉难以消化。我太低估这项考试了，也太低估计算机专业硕士入学的内卷程度。</p></li><li><p>漫长的复习时间中，我屡次感到厌倦。那种在高中时期所感受过的虚无之感，又缓慢的席卷上来。不是一个明确的闪回，而是像蒙尘那样缓慢堆积的麻木。待到回过神来，已经在慢性压力下苦苦挣扎，在沉没成本面前犹豫不决。</p></li><li><p>尤其是考研报名的那一刻，我曾无数次推迟的模拟考，和估分后的目标院校选择，此时已然兵临城下，避无可避。</p></li><li><p>于是我逃离了，数次。我失眠了，数次，最后还出现了极端的作息失调——昼夜颠倒，而后又倒回。但是当我上午时分入睡，傍晚之后起床，一股奇妙的惬意之感充斥着我的全身：</p><blockquote><p>于是我自暴自弃了，我干脆放开了熬夜，直到真正的困倦袭来之时才上床睡觉。我关闭了所有闹钟，只管睡到自然醒的那一刻。就这样过了三天，我的入睡时间从早上九点到十点，最后到达十二点。<br>但是在这样的作息节律下，我感觉自己的精神状态得到了显著的好转。尤其是今天，下午六点半起床后我感觉自己久违的，真的是久违的感到了一种发自内心的振奋还愉悦，身心充满了力量和喜悦。出门吃饭的路上不禁欢呼雀跃，耳机里的音乐也听起来更加动人了。<br>我感觉一切困难都存在解答，不再感到先前那样焦虑，不在为所谓意义和存在而消沉。夜晚的天空漆黑一片，那星光如同救赎一般照耀我身。美丽的夜晚独属于我，一想到如此我便感到振奋不已。</p></blockquote></li><li><p>如此精神上的解脱虽然没能带给我继续复习的动力，但确实把我从那种虚无中再次拉了出来，也是颇为神奇的一次经历。</p></li></ul><hr><ul><li><p>经历了这一切之后，我深深的感到，我实在是不能接受那套叙事。再怎么和我强调坚持的话语，上岸后的愿景，都无济于事。我就是无法忍受这样的东西。说我懒惰也好，无知也罢，我就是这样了。</p><blockquote><p>我感觉社会共识已经陷入了一种癫狂，我们如此努力，投入如此多时间不是因为热爱，不是因为可预期的回报，不是因为满足自身对知识的渴望，而仅仅是因为恐惧和焦虑。为了把另一个人挤出赛道我们花费了如此巨大的力气，学着一些不知道是什么臭狗屎的无聊应试知识和技巧。我理解供大于求的时候招人的实验室会需要候选人以某种方式证明自己，但当这种证明所需要的成本如此之大，其造成的对知识的异化如此之深的时候，我们居然还能保持这种狂热？<br>这简直叫我费解，但他们又是如此的笃定，如此的诚恳和勤劳，以至于我每每思索到此处总要掉转矛头来怀疑自己。</p></blockquote></li><li><p>谁能成想，这 408 烟雨浩渺的庞大知识体系中，第一个让我感到无所适从，难以坚持的居然是大家公认最简单的数据结构。</p><blockquote><p>计算机这块尤其恶心我和你讲<br>因为绝大部分的算法都不是凭空出现的<br>是基于一个需要解决的现实问题才被发明出来的<br>不研究现实问题单纯学算法在我看来是很恶心很无聊的行为<br>最典型的就是那几个图和相关的遍历和寻路算法<br>但是大家都在这么做<br>说实话我感觉这个现象远不止于计算机这个领域的教育<br>但说实话那些物理化学的什么理论可能实验条件很苛刻，没法频繁开展实验课<br>计算机的实验条件还不简单吗？<br>现在手机上都能写代码</p></blockquote></li><li><p>我实在是无法理解那些数据结构和算法背后的目的，标准化考试异化后的背诵知识在我的感受里味同嚼蜡，令我痛苦不堪。</p></li></ul><hr><ul><li><blockquote><p>你想去大厂接触海量并发场景吗？<br>你想去顶尖实验室玩那些昂贵的硬件吗？<br>你想在未来的职场上有更多的话语权去定义“什么是好的代码”吗？</p></blockquote></li><li><p>说实话，我不想。我只想安安静静的学习而已，我不在乎大厂里怎么样，我不需要昂贵的硬件，我不需要拥有什么“职场话语权”。我只想满足自己的好奇心，自己对知识的渴望，对创造的渴望，对意义的追求。</p></li><li><p>这也是为什么我对自己的考研之路感到如此的自我怀疑，与沉没成本心理的对抗是如此的令人纠结。我去查看了一些导师的介绍网站，看到他们所发的论文基本都是“深度学习灌水”之后更是倍受打击，一度想着直接放弃不考了。那些实验室感觉都是些挂羊头卖狗肉的货色，考上了进去也只是当一个论文生产流水线上的工人，真叫我作呕。</p></li></ul><hr><ul><li><p>不管怎么说，这样的精神折磨我都不想体验第二次了。我不推荐任何人考研，起码不要在需要付出如此巨大的代价的情况下选择考研。</p></li><li><p>我已经为我一年前轻率的决定付出了代价，即便是现在考完一周过后，那种深深的倦怠仍然留存在我的身体里。</p></li></ul><h2 id="关于校园之外的荒野"><a href="#关于校园之外的荒野" class="headerlink" title="关于校园之外的荒野"></a>关于校园之外的荒野</h2><ul><li><p>现在我毕业了，我不得不开始考虑进入社会。</p></li><li><p>曾经我不止一次因为这件事焦虑过，中学和本科时期均有，但我都已“忧虑未来没有意义”压下了情绪，尝试“着眼当下”。但现在这一刻已经来临了，而我依然没什么想法。</p></li><li><p>我对自己未来的方向感到迷茫。我感觉到了这个毕业后的时刻，我对社会的了解并没有比我在中学或者本科的时候加深多少。尽管有许多童话般天真的设想慢慢消解了，但那其实更加加剧了我的迷茫和不安，而没有提供多少具有建设性的意见。</p></li><li><p>或许这就是现实中人们原本的模样——跌跌撞撞的摸索着前进，像我这样内心之中“需完全搞懂后才可予以实践”的预设才是一种错误的，天真的，甚至有些傲慢的偏见。</p></li><li><p>眼下我能做什么呢？我对就业市场并不了解，我通过选择考研逃避了这件事。但是倘若我没有考上呢？等到我被迫面对这件事的时候，会不会太晚了呢？</p></li><li><p>算了，在这个考试刚刚结束的身心俱疲的节点，我不想考虑这些事情。</p></li><li><p>是的，我又一次逃避了。但我真的倦得很，要是将来我要为此付出什么代价的话，尽管来吧。我倦得很。</p></li></ul><h2 id="变化"><a href="#变化" class="headerlink" title="变化"></a>变化</h2><h3 id="Zen"><a href="#Zen" class="headerlink" title="Zen"></a>Zen</h3><ul><li><p>年底我更换了浏览器，从 Brave 转向了 Zen，契机是 Theo 的<a class="link"   href="https://www.youtube.com/watch?v=m1QrNF9wZao" >浏览器讨论视频<i class="fa-solid fa-arrow-up-right ml-[0.2em] font-light align-text-top text-[0.7em] link-icon"></i></a>。</p></li><li><p>不得不说，这个浏览器确实让我感到非常惊艳，尽管他在性能上饱受诟病——轻轻松松吃掉 10G 内存。但是我并不缺内存（32G），事实上大部分时候我的日常软件（除开浏览器），只能占用 5 个 G 左右，因此剩下的 20+G 内存事实上是被浪费了。</p></li><li><p>最令我感到满意的事实上是他彻底删去了浏览器常见的顶栏，并且在 compact mode 下连侧栏都被隐藏，因而使得用户获得了一个非常洁净，纯粹的浏览体验，所谓 “Zen”。</p></li><li><p>我基本没遇到 Firefox 引起的兼容性问题，加之我对内存问题并不敏感，因此这成为了我的新主力浏览器。</p></li><li><p>不过我仍然留着 Brave，因为 Zen 在 oopz 上似乎有些问题，无法读取到麦克风输入。以及为了我所熟悉的那套 chromium devtools。</p></li></ul><h3 id="Spotify"><a href="#Spotify" class="headerlink" title="Spotify"></a>Spotify</h3><ul><li><p>由于网易云封禁了所有使用第三方客户端的用户，我不得不另寻他就。 在群友的推荐下我选择了 Spotify，用了一些特殊手段购买了便宜的一年会员。</p></li><li><p>我不得不说这个软件的体验事实上是不如网易云的，甚至比不上它的那些第三方客户端。</p><ul><li>首先是卡顿，我实在是无法理解为什么一个音乐软件的卡顿能如此严重，每次我打开一个歌单或者进行一次搜索都要加载好久。</li><li>其次是在 Linux 端的 Spotify 就像是个 beta 版，时不时就会崩溃。尽管背景中仍在放着音乐，它那彻底卡死的 UI 界面足以让你明白，它又崩溃了。</li><li>最后是交互逻辑，我真是搞不懂英文软件怎么都是这种德行，Spotify，YouTube，Apple Music 都是这样，交互逻辑极其反人类令人难以理解，同时缺少很多在我看来理所应当存在的功能（例如对当前播放列表的自定义）。国内的各种软件的缺点在于功能过度臃肿，而国外这些软件给我的感觉就是他简直拒绝交互。</li></ul></li></ul><h2 id="结语"><a href="#结语" class="headerlink" title="结语"></a>结语</h2><ul><li><p>这是一个无聊的人所过的无聊的一年。丧失了意义，丧失了动力。</p></li><li><p>前年好不容易从痛苦中解脱的我，好像又落入了另一个关于虚无的苦难循环中，世事无常呀。</p></li><li><p>但是好在，这一切已经结束了。我只需休息片刻，控制权仍在我的手里，我需要，休息片刻，现在。</p></li></ul>]]>
    </content>
    <id>https://blog.imlast.top/2025/12/27/2025-Summary/</id>
    <link href="https://blog.imlast.top/2025/12/27/2025-Summary/"/>
    <published>2025-12-27T18:03:00.000Z</published>
    <summary>这是一个解冻之夜，一个疾风之夜</summary>
    <title>2025 年终总结</title>
    <updated>2025-12-27T18:03:00.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>Last</name>
    </author>
    <category term="工具使用" scheme="https://blog.imlast.top/categories/%E5%B7%A5%E5%85%B7%E4%BD%BF%E7%94%A8/"/>
    <category term="wayland" scheme="https://blog.imlast.top/tags/wayland/"/>
    <category term="linuxqq" scheme="https://blog.imlast.top/tags/linuxqq/"/>
    <category term="shell" scheme="https://blog.imlast.top/tags/shell/"/>
    <content>
      <![CDATA[<div class="callout callout--titled danger mb-4 rounded-small shadow-redefine-flat bg-(--callout-bg-color) p-3 pl-1 relative flex flex-row gap-2"><div role="none" class="rounded-full self-stretch w-0.5 bg-(--callout-primary-color) shrink-0 opacity-60"></div><div class="flex flex-col gap-2"><div class="callout__title flex items-center gap-2 font-semibold tracking-tight"><i class="callout__icon fa-clock leading-none text-(--callout-primary-color) text-sm shrink-0"></i> <strong>更新</strong></div><div class="callout__content markdown-body flex-1 min-w-0"><ul><li>该 Shell 脚本因改来改去 Bug 频出，因此近日笔者细细研究了一番这其中的古怪机制，详情见 <a href="https://blog.imlast.top/2026/02/13/clipboard-sync/">这篇博文</a></li></ul></div></div></div><h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><ul><li><p>不知从哪个版本开始，Linuxqq 在 Wayland 下的粘贴板出现了一个奇怪的问题：可以将 qq 中的文本复制到系统粘贴板（cliphist），但是无法将系统粘贴板上的内容粘贴进 qq 中。</p></li><li><p>由于问题出现当时我正好将 Linuxqq 换成了 <code>linuxqq-nt-bwarp</code>，于是理所当然的认为这是因为 bwarp 沙盒化引起的，乍看之下也挺合理。</p></li><li><p>直到今天在群里看到群主（应要求，不透露 id）发了一份用来同步 <code>xclip</code> 和 <code>cliphist</code> 粘贴板内容的脚本，这才恍然大悟——沟槽的 tx，虽然 linuxqq 基于的 Electron 已经默认使用 Wayland 协议，但其复制仍然选择将内容输出到 <code>xclip</code>。</p></li></ul><hr><div class="callout callout--titled info mb-4 rounded-small shadow-redefine-flat bg-(--callout-bg-color) p-3 pl-1 relative flex flex-row gap-2"><div role="none" class="rounded-full self-stretch w-0.5 bg-(--callout-primary-color) shrink-0 opacity-60"></div><div class="flex flex-col gap-2"><div class="callout__title flex items-center gap-2 font-semibold tracking-tight"><i class="callout__icon fa-circle-info leading-none text-(--callout-primary-color) text-sm shrink-0"></i> 查看</div><div class="callout__content markdown-body flex-1 min-w-0"><p>可以使用 <code>xclip -sel clip -o</code> 来查看 xlip 粘贴板中的内容。</p></div></div></div><h2 id="脚本"><a href="#脚本" class="headerlink" title="脚本"></a>脚本</h2><ul><li><p>脚本代码如下：</p>  <details class="relative my-4 border border-border-color bg-second-background-color rounded-md  blue" data-header-exclude><summary class="px-4 py-2 rounded-md shadow-[0_0_2px_0_var(--shadow-color-1)] cursor-pointer not-markdown"><i class="fa-solid fa-chevron-right"></i>clipboard_sync.sh</summary><div class="content p-4 "><div class="code-container" data-rel="Bash"><figure class="iseeu highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br><span class="line">141</span><br><span class="line">142</span><br><span class="line">143</span><br><span class="line">144</span><br><span class="line">145</span><br><span class="line">146</span><br><span class="line">147</span><br><span class="line">148</span><br><span class="line">149</span><br><span class="line">150</span><br><span class="line">151</span><br><span class="line">152</span><br><span class="line">153</span><br><span class="line">154</span><br><span class="line">155</span><br><span class="line">156</span><br><span class="line">157</span><br><span class="line">158</span><br><span class="line">159</span><br><span class="line">160</span><br><span class="line">161</span><br><span class="line">162</span><br><span class="line">163</span><br><span class="line">164</span><br><span class="line">165</span><br><span class="line">166</span><br><span class="line">167</span><br><span class="line">168</span><br><span class="line">169</span><br><span class="line">170</span><br><span class="line">171</span><br><span class="line">172</span><br><span class="line">173</span><br><span class="line">174</span><br><span class="line">175</span><br><span class="line">176</span><br><span class="line">177</span><br><span class="line">178</span><br><span class="line">179</span><br><span class="line">180</span><br><span class="line">181</span><br><span class="line">182</span><br><span class="line">183</span><br><span class="line">184</span><br><span class="line">185</span><br><span class="line">186</span><br><span class="line">187</span><br><span class="line">188</span><br><span class="line">189</span><br><span class="line">190</span><br><span class="line">191</span><br><span class="line">192</span><br><span class="line">193</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#!/bin/bash</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># Lock file to prevent multiple instances</span></span><br><span class="line">LOCK_DIR=<span class="string">&quot;<span class="variable">$&#123;XDG_RUNTIME_DIR:-/tmp&#125;</span>&quot;</span></span><br><span class="line">LOCK_FILE=<span class="string">&quot;<span class="variable">$LOCK_DIR</span>/clipboard_sync.lock&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="built_in">exec</span> 200&gt;<span class="string">&quot;<span class="variable">$LOCK_FILE</span>&quot;</span></span><br><span class="line">flock -n 200 || &#123;</span><br><span class="line">    <span class="built_in">echo</span> <span class="string">&quot;Error: Another instance of the script is already running.&quot;</span> &gt;&amp;2</span><br><span class="line">    <span class="built_in">exit</span> 1</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment"># Temp file path</span></span><br><span class="line">TEMP_IMG=<span class="string">&quot;<span class="variable">$LOCK_DIR</span>/clip_sync_buffer&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># The interval in seconds for checking the clipboard</span></span><br><span class="line">INTERVAL=0.5</span><br><span class="line"></span><br><span class="line"><span class="comment"># Color definitions for logging</span></span><br><span class="line">COLOR_RESET=<span class="string">&quot;\033[0m&quot;</span></span><br><span class="line">COLOR_GREEN=<span class="string">&quot;\033[32m&quot;</span></span><br><span class="line">COLOR_BLUE=<span class="string">&quot;\033[34m&quot;</span></span><br><span class="line">COLOR_YELLOW=<span class="string">&quot;\033[33m&quot;</span></span><br><span class="line">COLOR_CYAN=<span class="string">&quot;\033[36m&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># General log function</span></span><br><span class="line"><span class="function"><span class="title">log</span></span>() &#123;</span><br><span class="line">    <span class="built_in">local</span> timestamp=$(<span class="built_in">date</span> <span class="string">&#x27;+%H:%M:%S&#x27;</span>)</span><br><span class="line">    <span class="built_in">echo</span> -e <span class="string">&quot;<span class="variable">$&#123;COLOR_CYAN&#125;</span>[<span class="variable">$timestamp</span>]<span class="variable">$&#123;COLOR_RESET&#125;</span> <span class="variable">$1</span>&quot;</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment"># Log function specific to sync events</span></span><br><span class="line"><span class="function"><span class="title">log_sync</span></span>() &#123;</span><br><span class="line">    <span class="built_in">local</span> direction=<span class="variable">$1</span></span><br><span class="line">    <span class="built_in">local</span> <span class="built_in">type</span>=<span class="variable">$2</span></span><br><span class="line">    <span class="built_in">local</span> format=<span class="variable">$3</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">case</span> <span class="string">&quot;<span class="variable">$direction</span>&quot;</span> <span class="keyword">in</span></span><br><span class="line">        <span class="string">&quot;x11-&gt;wl&quot;</span>)</span><br><span class="line">            <span class="built_in">echo</span> -e <span class="string">&quot;<span class="variable">$&#123;COLOR_CYAN&#125;</span>[<span class="subst">$(date &#x27;+%H:%M:%S&#x27;)</span>]<span class="variable">$&#123;COLOR_RESET&#125;</span> <span class="variable">$&#123;COLOR_GREEN&#125;</span>✓<span class="variable">$&#123;COLOR_RESET&#125;</span> X11 → Wayland | <span class="variable">$&#123;COLOR_YELLOW&#125;</span><span class="variable">$&#123;type&#125;</span><span class="variable">$&#123;COLOR_RESET&#125;</span><span class="variable">$&#123;format&#125;</span>&quot;</span></span><br><span class="line">            ;;</span><br><span class="line">        <span class="string">&quot;wl-&gt;x11&quot;</span>)</span><br><span class="line">            <span class="built_in">echo</span> -e <span class="string">&quot;<span class="variable">$&#123;COLOR_CYAN&#125;</span>[<span class="subst">$(date &#x27;+%H:%M:%S&#x27;)</span>]<span class="variable">$&#123;COLOR_RESET&#125;</span> <span class="variable">$&#123;COLOR_BLUE&#125;</span>✓<span class="variable">$&#123;COLOR_RESET&#125;</span> Wayland → X11 | <span class="variable">$&#123;COLOR_YELLOW&#125;</span><span class="variable">$&#123;type&#125;</span><span class="variable">$&#123;COLOR_RESET&#125;</span><span class="variable">$&#123;format&#125;</span>&quot;</span></span><br><span class="line">            ;;</span><br><span class="line">    <span class="keyword">esac</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment"># Function to check for required command dependencies</span></span><br><span class="line"><span class="function"><span class="title">check_dependencies</span></span>() &#123;</span><br><span class="line">    <span class="built_in">local</span> missing_deps=()</span><br><span class="line">    <span class="keyword">for</span> cmd <span class="keyword">in</span> xclip wl-paste wl-copy <span class="built_in">sha256sum</span>; <span class="keyword">do</span></span><br><span class="line">        <span class="keyword">if</span> ! <span class="built_in">command</span> -v <span class="string">&quot;<span class="variable">$cmd</span>&quot;</span> &amp;&gt;/dev/null; <span class="keyword">then</span></span><br><span class="line">            missing_deps+=(<span class="string">&quot;<span class="variable">$cmd</span>&quot;</span>)</span><br><span class="line">        <span class="keyword">fi</span></span><br><span class="line">    <span class="keyword">done</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> [[ <span class="variable">$&#123;#missing_deps[@]&#125;</span> -gt 0 ]]; <span class="keyword">then</span></span><br><span class="line">        <span class="built_in">echo</span> <span class="string">&quot;Error: Missing required dependencies: <span class="variable">$&#123;missing_deps[*]&#125;</span>&quot;</span> &gt;&amp;2</span><br><span class="line">        <span class="built_in">exit</span> 1</span><br><span class="line">    <span class="keyword">fi</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment"># Debounce variables</span></span><br><span class="line">last_text=<span class="string">&quot;&quot;</span></span><br><span class="line">last_x11_img_hash=<span class="string">&quot;&quot;</span>  <span class="comment"># Hash of the last image synced from X11 to Wayland</span></span><br><span class="line">last_wl_img_hash=<span class="string">&quot;&quot;</span>   <span class="comment"># Hash of the last image synced from Wayland to X11</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># Main sync loop</span></span><br><span class="line"><span class="function"><span class="title">clipboard_sync</span></span>() &#123;</span><br><span class="line">    <span class="keyword">while</span> <span class="literal">true</span>; <span class="keyword">do</span></span><br><span class="line">        img_synced=<span class="literal">false</span>  <span class="comment"># Flag to track if an image was synced in this iteration</span></span><br><span class="line"></span><br><span class="line">        <span class="comment"># -------- Image Sync: X11 → Wayland --------</span></span><br><span class="line">        x11_targets=$(xclip -selection clipboard -t TARGETS -o 2&gt;/dev/null || <span class="literal">true</span>)</span><br><span class="line">        x11_img_type=<span class="string">&quot;&quot;</span></span><br><span class="line">        <span class="keyword">if</span> <span class="built_in">echo</span> <span class="string">&quot;<span class="variable">$x11_targets</span>&quot;</span> | grep -q <span class="string">&quot;image/png&quot;</span>; <span class="keyword">then</span></span><br><span class="line">            x11_img_type=<span class="string">&quot;image/png&quot;</span></span><br><span class="line">        <span class="keyword">elif</span> <span class="built_in">echo</span> <span class="string">&quot;<span class="variable">$x11_targets</span>&quot;</span> | grep -q <span class="string">&quot;image/jpeg&quot;</span>; <span class="keyword">then</span></span><br><span class="line">            x11_img_type=<span class="string">&quot;image/jpeg&quot;</span></span><br><span class="line">        <span class="keyword">elif</span> <span class="built_in">echo</span> <span class="string">&quot;<span class="variable">$x11_targets</span>&quot;</span> | grep -q <span class="string">&quot;image/gif&quot;</span>; <span class="keyword">then</span></span><br><span class="line">            x11_img_type=<span class="string">&quot;image/gif&quot;</span></span><br><span class="line">        <span class="keyword">fi</span></span><br><span class="line"></span><br><span class="line">        <span class="keyword">if</span> [[ -n <span class="string">&quot;<span class="variable">$x11_img_type</span>&quot;</span> ]]; <span class="keyword">then</span></span><br><span class="line">            <span class="keyword">if</span> xclip -selection clipboard -t <span class="string">&quot;<span class="variable">$x11_img_type</span>&quot;</span> -o &gt; <span class="string">&quot;TEMP_IMG&quot;</span> 2&gt;/dev/null; <span class="keyword">then</span></span><br><span class="line"></span><br><span class="line">                <span class="comment"># image output file exists</span></span><br><span class="line">                <span class="keyword">if</span> [[ -s <span class="string">&quot;<span class="variable">$TEMP_IMG</span>&quot;</span> ]]; <span class="keyword">then</span></span><br><span class="line">                    x11_img_hash=$(xclip -selection clipboard -t <span class="string">&quot;<span class="variable">$x11_img_type</span>&quot;</span> -o 2&gt;/dev/null | <span class="built_in">sha256sum</span> | awk <span class="string">&#x27;&#123;print $1&#125;&#x27;</span>)</span><br><span class="line"></span><br><span class="line">                    <span class="keyword">if</span> [[ -n <span class="string">&quot;<span class="variable">$x11_img_hash</span>&quot;</span> &amp;&amp; <span class="string">&quot;<span class="variable">$x11_img_hash</span>&quot;</span> != <span class="string">&quot;<span class="variable">$last_x11_img_hash</span>&quot;</span> ]]; <span class="keyword">then</span></span><br><span class="line"></span><br><span class="line">                        <span class="comment"># use temp file as buffer instead of using pipeline directly to avoid image cut off</span></span><br><span class="line">                        wl-copy -t <span class="string">&quot;<span class="variable">$x11_img_type</span>&quot;</span> &lt; <span class="string">&quot;<span class="variable">$TEMP_IMG</span>&quot;</span></span><br><span class="line"></span><br><span class="line">                        last_x11_img_hash=<span class="string">&quot;<span class="variable">$x11_img_hash</span>&quot;</span></span><br><span class="line">                        last_wl_img_hash=<span class="string">&quot;<span class="variable">$x11_img_hash</span>&quot;</span></span><br><span class="line">                        img_synced=<span class="literal">true</span></span><br><span class="line">                        log_sync <span class="string">&quot;x11-&gt;wl&quot;</span> <span class="string">&quot;Image&quot;</span> <span class="string">&quot; (<span class="variable">$x11_img_type</span>)&quot;</span></span><br><span class="line">                    <span class="keyword">fi</span></span><br><span class="line">                <span class="keyword">fi</span></span><br><span class="line"></span><br><span class="line">            <span class="keyword">fi</span></span><br><span class="line">        <span class="keyword">fi</span></span><br><span class="line"></span><br><span class="line">        <span class="comment"># -------- Image Sync: Wayland → X11 --------</span></span><br><span class="line">        <span class="comment"># Only check Wayland → X11 if no image sync has occurred in this iteration</span></span><br><span class="line">        <span class="keyword">if</span> [[ <span class="string">&quot;<span class="variable">$img_synced</span>&quot;</span> == <span class="literal">false</span> ]]; <span class="keyword">then</span></span><br><span class="line">            wl_types=$(wl-paste --list-types 2&gt;/dev/null || <span class="literal">true</span>)</span><br><span class="line">            wl_img_type=<span class="string">&quot;&quot;</span></span><br><span class="line">            <span class="keyword">if</span> <span class="built_in">echo</span> <span class="string">&quot;<span class="variable">$wl_types</span>&quot;</span> | grep -q <span class="string">&quot;image/png&quot;</span>; <span class="keyword">then</span></span><br><span class="line">                wl_img_type=<span class="string">&quot;image/png&quot;</span></span><br><span class="line">            <span class="keyword">elif</span> <span class="built_in">echo</span> <span class="string">&quot;<span class="variable">$wl_types</span>&quot;</span> | grep -q <span class="string">&quot;image/jpeg&quot;</span>; <span class="keyword">then</span></span><br><span class="line">                wl_img_type=<span class="string">&quot;image/jpeg&quot;</span></span><br><span class="line">            <span class="keyword">elif</span> <span class="built_in">echo</span> <span class="string">&quot;<span class="variable">$wl_types</span>&quot;</span> | grep -q <span class="string">&quot;image/gif&quot;</span>; <span class="keyword">then</span></span><br><span class="line">                wl_img_type=<span class="string">&quot;image/gif&quot;</span></span><br><span class="line">            <span class="keyword">fi</span></span><br><span class="line"></span><br><span class="line">            <span class="keyword">if</span> [[ -n <span class="string">&quot;<span class="variable">$wl_img_type</span>&quot;</span> ]]; <span class="keyword">then</span></span><br><span class="line">                <span class="comment"># Pipe binary data to calculate hash for debouncing</span></span><br><span class="line">                <span class="comment"># wl_img_hash=$(wl-paste -t &quot;$wl_img_type&quot; 2&gt;/dev/null | tee &gt;(sha256sum | awk &#x27;&#123;print $1&#125;&#x27; &gt; /tmp/wl_hash_$$) | cat &gt; /dev/null; cat /tmp/wl_hash_$$ 2&gt;/dev/null; rm -f /tmp/wl_hash_$$ 2&gt;/dev/null)</span></span><br><span class="line"></span><br><span class="line">                <span class="keyword">if</span> wl-paste -t <span class="string">&quot;<span class="variable">$wl_img_type</span>&quot;</span> &gt; <span class="string">&quot;<span class="variable">$TEMP_IMG</span>&quot;</span> 2&gt;/dev/null; <span class="keyword">then</span></span><br><span class="line">                    <span class="keyword">if</span> [[ -s <span class="string">&quot;<span class="variable">$TEMP_IMG</span>&quot;</span> ]]; <span class="keyword">then</span></span><br><span class="line">                        wl_img_hash=$(<span class="built_in">sha256sum</span> <span class="string">&quot;<span class="variable">$TEMP_IMG</span>&quot;</span> | awk <span class="string">&#x27;&#123;print $1&#125;&#x27;</span>)</span><br><span class="line"></span><br><span class="line">                        <span class="comment"># If the Wayland image is different from the last synced one, sync it to X11</span></span><br><span class="line">                        <span class="keyword">if</span> [[ -n <span class="string">&quot;<span class="variable">$wl_img_hash</span>&quot;</span> &amp;&amp; <span class="string">&quot;<span class="variable">$wl_img_hash</span>&quot;</span> != <span class="string">&quot;<span class="variable">$last_wl_img_hash</span>&quot;</span> ]]; <span class="keyword">then</span></span><br><span class="line">                            xclip -selection clipboard -t <span class="string">&quot;wl_img_type&quot;</span> -i &lt; <span class="string">&quot;<span class="variable">$TEMP_IMG</span>&quot;</span></span><br><span class="line"></span><br><span class="line">                            last_wl_img_hash=<span class="string">&quot;<span class="variable">$wl_img_hash</span>&quot;</span></span><br><span class="line">                            last_x11_img_hash=<span class="string">&quot;<span class="variable">$wl_img_hash</span>&quot;</span></span><br><span class="line">                            img_synced=<span class="literal">true</span>  <span class="comment"># Mark image as synced to skip text sync in this iteration</span></span><br><span class="line">                            log_sync <span class="string">&quot;wl-&gt;x11&quot;</span> <span class="string">&quot;Image&quot;</span> <span class="string">&quot; (<span class="variable">$wl_img_type</span>)&quot;</span></span><br><span class="line">                        <span class="keyword">fi</span></span><br><span class="line">                    <span class="keyword">fi</span></span><br><span class="line">                <span class="keyword">fi</span></span><br><span class="line"></span><br><span class="line">            <span class="keyword">fi</span></span><br><span class="line">        <span class="keyword">fi</span></span><br><span class="line"></span><br><span class="line">        <span class="comment"># -------- Text Sync --------</span></span><br><span class="line">        <span class="comment"># Only perform text sync if no image was synced in this iteration</span></span><br><span class="line">        <span class="keyword">if</span> [[ <span class="string">&quot;<span class="variable">$img_synced</span>&quot;</span> == <span class="literal">false</span> ]]; <span class="keyword">then</span></span><br><span class="line">            text_synced=<span class="literal">false</span>  <span class="comment"># Flag to track if text was synced in this iteration</span></span><br><span class="line"></span><br><span class="line">            x11_targets=$(xclip -selection clipboard -t TARGETS -o 2&gt;/dev/null || <span class="literal">true</span>)</span><br><span class="line"></span><br><span class="line">            <span class="comment"># Only read from X11 clipboard if it likely contains text to avoid warnings from binary data</span></span><br><span class="line">            <span class="keyword">if</span> <span class="built_in">echo</span> <span class="string">&quot;x11_targets&quot;</span> | grep -qE <span class="string">&quot;image/png|image/jpeg|image/bmp|image/tiff&quot;</span>; <span class="keyword">then</span></span><br><span class="line">                x11_text=<span class="string">&quot;&quot;</span></span><br><span class="line">            <span class="keyword">else</span></span><br><span class="line">                x11_text=$(xclip -selection clipboard -t UTF8_STRING -o 2&gt;/dev/null || <span class="literal">true</span>)</span><br><span class="line">            <span class="keyword">fi</span></span><br><span class="line"></span><br><span class="line">            current_text=$(wl-paste --<span class="built_in">type</span> text/plain 2&gt;/dev/null || <span class="literal">true</span>)</span><br><span class="line"></span><br><span class="line">            <span class="keyword">if</span> [[ -n <span class="string">&quot;<span class="variable">$x11_text</span>&quot;</span> &amp;&amp; <span class="string">&quot;<span class="variable">$x11_text</span>&quot;</span> != <span class="string">&quot;<span class="variable">$last_text</span>&quot;</span> &amp;&amp; <span class="string">&quot;<span class="variable">$x11_text</span>&quot;</span> != <span class="string">&quot;<span class="variable">$current_text</span>&quot;</span> ]]; <span class="keyword">then</span></span><br><span class="line">                <span class="keyword">if</span> [[ ! <span class="string">&quot;<span class="variable">$x11_text</span>&quot;</span> =~ \x00 ]]; <span class="keyword">then</span></span><br><span class="line">                    <span class="comment"># use `printf` for special characters</span></span><br><span class="line">                    <span class="built_in">printf</span> <span class="string">&quot;%s&quot;</span> <span class="string">&quot;<span class="variable">$x11_text</span>&quot;</span> | wl-copy --<span class="built_in">type</span> text/plain</span><br><span class="line">                    last_text=<span class="string">&quot;<span class="variable">$x11_text</span>&quot;</span></span><br><span class="line">                    text_synced=<span class="literal">true</span></span><br><span class="line">                    log_sync <span class="string">&quot;x11-&gt;wl&quot;</span> <span class="string">&quot;Text&quot;</span> <span class="string">&quot; \&quot;<span class="variable">$preview</span>\&quot;&quot;</span></span><br><span class="line">                <span class="keyword">fi</span></span><br><span class="line">            <span class="keyword">fi</span></span><br><span class="line"></span><br><span class="line">            <span class="comment"># Only check Wayland → X11 if no text sync has occurred from the other direction</span></span><br><span class="line">            <span class="keyword">if</span> [[ <span class="string">&quot;<span class="variable">$text_synced</span>&quot;</span> == <span class="literal">false</span> &amp;&amp; -n <span class="string">&quot;<span class="variable">$current_text</span>&quot;</span> &amp;&amp; <span class="string">&quot;<span class="variable">$current_text</span>&quot;</span> != <span class="string">&quot;<span class="variable">$last_text</span>&quot;</span> &amp;&amp; <span class="string">&quot;<span class="variable">$x11_text</span>&quot;</span> != <span class="string">&quot;<span class="variable">$current_text</span>&quot;</span> ]]; <span class="keyword">then</span></span><br><span class="line">                <span class="comment"># use `printf` for special characters</span></span><br><span class="line">                <span class="built_in">printf</span> <span class="string">&quot;%s&quot;</span> <span class="string">&quot;<span class="variable">$current_text</span>&quot;</span> | xclip -selection clipboard -t UTF8_STRING -i</span><br><span class="line">                last_text=<span class="string">&quot;<span class="variable">$current_text</span>&quot;</span></span><br><span class="line">                log_sync <span class="string">&quot;wl-&gt;x11&quot;</span> <span class="string">&quot;Text&quot;</span> <span class="string">&quot; \&quot;<span class="variable">$preview</span>\&quot;&quot;</span></span><br><span class="line">            <span class="keyword">fi</span></span><br><span class="line">        <span class="keyword">fi</span></span><br><span class="line"></span><br><span class="line">        <span class="built_in">sleep</span> <span class="variable">$INTERVAL</span></span><br><span class="line">    <span class="keyword">done</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment"># Set up cleanup on exit (releases the lock file)</span></span><br><span class="line"><span class="built_in">trap</span> <span class="string">&#x27;exit&#x27;</span> INT TERM EXIT</span><br><span class="line"></span><br><span class="line"><span class="comment"># Check for dependencies</span></span><br><span class="line">check_dependencies</span><br><span class="line"></span><br><span class="line"><span class="built_in">log</span> <span class="string">&quot;Clipboard sync service started&quot;</span></span><br><span class="line"><span class="built_in">log</span> <span class="string">&quot;Monitoring clipboard sync between Wayland ↔ X11...&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="built_in">log</span> <span class="string">&quot;Service started on lock: <span class="variable">$LOCK_FILE</span>&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># Start the sync service</span></span><br><span class="line">clipboard_sync</span><br></pre></td></tr></table></figure></div></div></details></li></ul><hr><ul><li><p>这个脚本的思路很简单：每间隔 <code>$INTERVAL</code> 的时间，就同步一次两个剪贴板中的内容。同时，利用哈希算法来确保图片的同步过程中不会出现无限循环。</p></li><li><p>同时，笔者加入了使用 <code>flock</code> 的片段来确保不会出现该脚本在同一时间被运行多次的情况。</p>  <div class="code-container" data-rel="Bash"><figure class="iseeu highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># Lock file to prevent multiple instances</span></span><br><span class="line">LOCK_DIR=<span class="string">&quot;<span class="variable">$&#123;XDG_RUNTIME_DIR:-/tmp&#125;</span>&quot;</span></span><br><span class="line">LOCK_FILE=<span class="string">&quot;<span class="variable">$LOCK_DIR</span>/clipboard_sync.lock&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="built_in">exec</span> 200&gt;<span class="string">&quot;<span class="variable">$LOCK_FILE</span>&quot;</span></span><br><span class="line">flock -n 200 || &#123;</span><br><span class="line">    <span class="built_in">echo</span> <span class="string">&quot;Error: Another instance of the script is already running.&quot;</span> &gt;&amp;2</span><br><span class="line">    <span class="built_in">exit</span> 1</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment"># ...</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="built_in">trap</span> <span class="string">&#x27;exit&#x27;</span> INT TERM EXIT</span><br></pre></td></tr></table></figure></div></li></ul><h2 id="后台运行"><a href="#后台运行" class="headerlink" title="后台运行"></a>后台运行</h2><ul><li><p>笔者使用的是 Hyprland，因此只需在配置文件中加入：</p>  <div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"># Start clipboard sync service (for linuxqq)</span><br><span class="line">exec = ~/path/to/clipboard_sync.sh</span><br></pre></td></tr></table></figure></div></li><li><p>即可。</p></li></ul>]]>
    </content>
    <id>https://blog.imlast.top/2025/10/15/linuxqq-clipboard-issue/</id>
    <link href="https://blog.imlast.top/2025/10/15/linuxqq-clipboard-issue/"/>
    <published>2025-10-15T09:07:43.000Z</published>
    <summary>解决 Linuxqq 在 Wayland 协议下粘贴板无法正常工作的问题</summary>
    <title>Linuxqq 粘贴板同步问题</title>
    <updated>2025-10-15T09:07:43.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>Last</name>
    </author>
    <category term="随笔" scheme="https://blog.imlast.top/categories/%E9%9A%8F%E7%AC%94/"/>
    <category term="linux" scheme="https://blog.imlast.top/tags/linux/"/>
    <content>
      <![CDATA[<ul><li><p>在刚接触 Arch Linux 的时候我也像许多人一样，痴迷于美化自己的桌面系统。从 KDE 的自定义组件到 bspwm 的脚本配置，最后到 hyprland 各种绚丽的动效。此外，还有很多 neovim 的插件和快捷键配置，例如 neo-tree 或者 codesnap，最近一次的配置大修更是从一系列主流插件转向了 folke 的 <code>snacks.nvim</code> 。（哎 folke 的大手）</p></li><li><p>不得不说，折腾这些美化和快捷键之类的配置确实有一种魔力，让我着迷。诚然，摆弄这些东西并予以一定程度的客制化确实能够帮助用户更加了解他们的系统和桌面环境操作。其对于生产力的作用虽然确实不能说完全没有，但也只是“可有可无”的程度而已。在这其中投入大量的时间并非明智之举。</p></li></ul><hr><ul><li><p>我想，之所以这样的行为如此令人着迷，大概是因为更改配置时产生的“即时反馈”。相较于对一些艰深知识的漫长学习，折腾配置所产生的成果来的要快多了——只需浏览官方文档（又或者是从别人哪里抄来一套配置），更改几个选项便能立竿见影的看到其实现的效果，这确实很令人着迷，让人产生一种“自己的系统全然尽在掌握”的奇妙感觉。</p></li><li><p>但事实上，这种掌控感一定程度上可以说是“假的”——折腾配置并没有带来对于桌面环境本身或者系统底层运行原理的理解，能够通过修改各种配置得到一个美观的桌面并不代表理解了 wayland 协议或者各种动效的实现原理。</p></li><li><p>不停的更换桌面环境，但是始终没有能力为其提供一个 patch 或者实现一个 new feature。安装了几十个插件，但是最终也没有能力编写一个属于自己的插件，又或者对于 neovim 的 lsp 实现一无所知（……literally me）。</p></li></ul><hr><ul><li><p>不可否认，在折腾桌面环境的美化和 neovim 插件的过程中，我某些方面的能力的确得到了提升。例如：查文档定位需求&#x2F;问题的能力和英语阅读能力。</p></li><li><p>但是这种提升在我看来，并不一定非要通过折腾桌面美化来获得。直接参与优秀公开课的学习或者实习对于上述能力的提升更加立竿见影。相较于投入在 ricing 上的大量时间，所获得的回报实在是有点不成比例了。</p></li><li><p>不过就这方面来说，可能没那么绝对。毕竟有时候过于缺乏基础能力的情况下盲目上手一些专业性和理论性较强的较难课程可能确实比较困难。“兴趣驱动学习”或许可以作为一个“过渡阶段”，帮助度过前期的困难期。</p></li></ul><hr><ul><li><p>或许在这个问题上，我们应当运用一下所谓的“二八定律”，即花费 20% 的时间来获取 80% 的效果。比如我现在就直接使用 ml4w 的 hyprland 配置并加以魔改以适应此前自己习惯的快捷键配置。在<a href="https://blog.imlast.top/2024/12/18/2nd-hyprland/">相关博客</a>中我当时也提到了在折腾桌面环境配置上所花费的大量时间并不是很值得：</p><blockquote><p>在初入 DE Rice 领域的时候，我曾坚信只有由自己亲手去写（缝） dotfiles，搞明白每一个配置选项和桌面环境的启动流程，才能保证在某个桌面组件出问题的时候可以快速定位到问题源头并予以修复。直接套用他人提供的配置文件看似在配置阶段省下了不少时间，实则会在将来的某一刻加倍偿还。<br>虽然如今我依然这样觉得，但是已然不愿意付出折腾系统美化所消耗的时间和精力代价了。</p></blockquote><blockquote><p>所以这次我选择了一种折中的方案：套用 mylinuxforwork 的 dotfile（该配置套件甚至在 Hyprland 的官方 wiki 中得到推荐），在此基础之上加入&#x2F;修改一些自己定制的配置方案。<br>当然，这一切也是基于我过去很长一段时间内使用 Bspwm 和上一次配置 Hyprland 的经验才能实现。毕竟，深度定制桌面环境配置的前提依然是能看懂各个配置项的作用。</p></blockquote></li><li><p>简而言之，稍微投入一定程度的努力，获得一定的效率提升和阅读文档能力之后便可收手了。再往后，各种细致定制化大部分情况也不过是重复此前的步骤，在各种桌面环境和编辑器&#x2F;插件间来回跳跃而已。</p></li></ul><hr><ul><li>又或者说，或许可以以此为入口，尝试对于 Linux 系统底层和各种桌面协议进行学习？这在大部分情况下感觉和折腾配置的初衷是矛盾的——大部分情况下人们只是想要即时反馈和绚丽的桌面以作为炫耀的资本而已。漫长且反馈具有一定延迟性的学习一定程度上来说是反人性的，单凭兴趣驱动恐怕很难支持一个人走那么远的距离。</li></ul><hr><ul><li>总的来说，折腾美化和配置在某种程度上确实可以提升用户日常操作的效率，以及文档阅读能力。但是这样的能力提升并不高效，且走不远。最终我们还是应该回归到对于更加“正经”或者说“传统”的 CS 技能和知识的学习上来。</li></ul>]]>
    </content>
    <id>https://blog.imlast.top/2025/06/22/configuration-engineering/</id>
    <link href="https://blog.imlast.top/2025/06/22/configuration-engineering/"/>
    <published>2025-06-22T14:10:27.000Z</published>
    <summary>所谓“配置工程师”的利与弊</summary>
    <title>关于所谓“配置工程师”</title>
    <updated>2025-06-22T14:10:27.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>Last</name>
    </author>
    <category term="笔记" scheme="https://blog.imlast.top/categories/%E7%AC%94%E8%AE%B0/"/>
    <category term="c" scheme="https://blog.imlast.top/tags/c/"/>
    <content>
      <![CDATA[<h2 id="起因"><a href="#起因" class="headerlink" title="起因"></a>起因</h2><ul><li><p>记得去年在完成 NJU PA 的时候，遇见一位群友在读取 elf 文件并校验魔数的时候遇到的问题：他读取出的魔数是字节颠倒的。</p></li><li><p>他读出的魔数是 <code>46 4c 45 7f</code>，但实际上应当为 <code>7f 45 4c 46</code>。从表面上看，就好像是因为小端序机器中内存的存储方式一样，以字节为单位颠倒过来。</p></li><li><p>我当时仔细检查他的代码后发现，他使用 <code>fread</code> 函数的时候出现了错误，填反了 <code>size</code> 和 <code>n_count</code> 这两个参数：</p>  <div class="code-container" data-rel="C"><figure class="iseeu highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">uint32_t</span> ident;</span><br><span class="line">FILE *fp = fopen(<span class="string">&quot;./a.out&quot;</span>, <span class="string">&quot;rb&quot;</span>);</span><br><span class="line"></span><br><span class="line">fread(&amp;ident, <span class="number">1</span>, <span class="number">4</span>, fp);</span><br></pre></td></tr></table></figure></div></li><li><p>我想当然的认为，这就是导致最后字节顺序倒转的原因。</p></li><li><p>但实际上当时我们都没有尝试调转这两个参数的位置后，输出的内容会不会恢复正常——那位朋友后来得知了可以 <code>#include &lt;elf.h&gt;</code> 并且使用其中定义好的数据结构承接 elf 文件中的内容，也就不需要这种方法了。</p>  <div class="code-container" data-rel="C"><figure class="iseeu highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">ElfW(Ehdr) ELF_header;</span><br><span class="line">fread(&amp;ELF_header, <span class="keyword">sizeof</span>(ELF_header), <span class="number">1</span>, file);</span><br><span class="line"><span class="comment">// check file ident</span></span><br><span class="line"><span class="keyword">if</span> (<span class="built_in">memcmp</span>(ELF_header.e_ident, ELFMAG, SELFMAG) != <span class="number">0</span>)</span><br><span class="line">    Assert(<span class="number">0</span>, ANSI_FMT(<span class="string">&quot;Not an ELF file: %s.&quot;</span>, ANSI_FG_RED), elfFile);</span><br></pre></td></tr></table></figure></div></li></ul><h2 id="实际原因"><a href="#实际原因" class="headerlink" title="实际原因"></a>实际原因</h2><ul><li><p>时隔半年我看到考研群里大伙在讨论大小端序，突然想起来上面这位朋友遇到的问题，便想着重现一下并且和群友们分享。但是我突然发现我始终无法获取一个相反的文件内容，不管 <code>size</code> 是不是 1。</p></li><li><p>询问大模型后我才恍然大悟，原来真正的问题并不在于 <code>fread</code>，而是用来存放读取到的内容的变量类型： <code>uint32_t</code>。</p></li><li><p>真正的原因在于从文件中读取的内容被存在了一个 <code>uint32_t</code> 类型的变量里，而这类变量在小端序机器的内存中存放的方式正是字节颠倒的。此时使用 <code>%x</code> 作为占位符将其输出时，就会得到一个字节颠倒的结果。</p></li><li><p>下面这两个函数可以很好的展示这一差异：</p>  <div class="code-container" data-rel="C"><figure class="iseeu highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">void</span> <span class="title function_">read_elf_uint</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="type">uint32_t</span> ident;</span><br><span class="line">    FILE *fp = fopen(<span class="string">&quot;./a.out&quot;</span>, <span class="string">&quot;rb&quot;</span>);</span><br><span class="line"></span><br><span class="line">    fread(&amp;ident, <span class="number">4</span>, <span class="number">1</span>, fp);</span><br><span class="line"></span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">&quot;reversed: 0x%x\n&quot;</span>, ident);</span><br><span class="line"></span><br><span class="line">    fclose(fp);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="type">void</span> <span class="title function_">read_elf</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="type">unsigned</span> <span class="type">char</span> ident[<span class="number">4</span>];</span><br><span class="line">    <span class="built_in">memset</span>(ident, <span class="number">0</span>, <span class="number">4</span>);</span><br><span class="line">    FILE *fp = fopen(<span class="string">&quot;./a.out&quot;</span>, <span class="string">&quot;rb&quot;</span>);</span><br><span class="line"></span><br><span class="line">    fread(ident, <span class="number">4</span>, <span class="number">1</span>, fp);</span><br><span class="line"></span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">&quot;correct: 0x&quot;</span>);</span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">0</span>; i &lt; <span class="number">4</span>; i++) &#123;</span><br><span class="line">        <span class="built_in">printf</span>(<span class="string">&quot;%x&quot;</span>, ident[i]);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">&quot;\n&quot;</span>);</span><br><span class="line"></span><br><span class="line">    fclose(fp);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div></li></ul><h2 id="感慨"><a href="#感慨" class="headerlink" title="感慨"></a>感慨</h2><ul><li><p>C 真是门艰深又麻烦的语言，为了灵活性导致了非常多隐形的复杂度。</p></li><li><p>又或者说，为了更好的，以更底层的方式操作计算机的工作，势必要忍受一些未被封装的复杂度。</p></li></ul>]]>
    </content>
    <id>https://blog.imlast.top/2025/05/14/edian/</id>
    <link href="https://blog.imlast.top/2025/05/14/edian/"/>
    <published>2025-05-14T14:03:05.000Z</published>
    <summary>小端序机器内存变量存储字节序的一种体现</summary>
    <title>记一次小端序内存的实际体现</title>
    <updated>2025-05-14T14:03:05.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>Last</name>
    </author>
    <category term="工具使用" scheme="https://blog.imlast.top/categories/%E5%B7%A5%E5%85%B7%E4%BD%BF%E7%94%A8/"/>
    <category term="embedded development" scheme="https://blog.imlast.top/tags/embedded-development/"/>
    <category term="arduino" scheme="https://blog.imlast.top/tags/arduino/"/>
    <category term="neovim" scheme="https://blog.imlast.top/tags/neovim/"/>
    <category term="LSP" scheme="https://blog.imlast.top/tags/LSP/"/>
    <content>
      <![CDATA[<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><ul><li>毕设选了个嵌入式开发的小项目，不是很想用笨重的 arduino IDE。</li><li>于是浪费了半个下午配置环境……水篇博客，不然这努力岂不是白费了（bushi</li></ul><h2 id="正文"><a href="#正文" class="headerlink" title="正文"></a>正文</h2><h3 id="问题描述"><a href="#问题描述" class="headerlink" title="问题描述"></a>问题描述</h3><ul><li><p>期初我尝试直接使用 <code>Mason</code> 安装 <code>arduino-language-server</code>，但即便按文档要求修改配置后仍然无法正常启动。</p></li><li><p>查阅相关 <a class="link"   href="https://github.com/arduino/arduino-language-server/pull/199#issuecomment-2453517587" >issues&#x2F;PR<i class="fa-solid fa-arrow-up-right ml-[0.2em] font-light align-text-top text-[0.7em] link-icon"></i></a> 之后发现，问题不在 <code>arduino-language-server</code> 自身，而是将其 integrate 进 Neovim 时所依赖的 <code>go-lsp</code> 上。</p><blockquote><p>The dependency (bugst&#x2F;go-lsp) used by the Arduino language server was updated (its go.mod was bumped to Go 1.22), and its behavior causes the language server to “fail to attach” or “quit” when Neovim v0.10+ attempts to start it. This manifests as errors in the Neovim logs (e.g. errors related to clangd startup and “locked (waiting clangd)”, even though the server may still work despite the warning message).</p></blockquote></li><li><p><a class="link"   href="https://github.com/speelbarrow" >speelbarrow<i class="fa-solid fa-arrow-up-right ml-[0.2em] font-light align-text-top text-[0.7em] link-icon"></i></a> 将 <code>arduino-language-server</code> 依赖的 <code>go-lsp</code> 更换为了其 patch 过的版本：</p>  <div class="code-container" data-rel="Diff"><figure class="iseeu highlight diff"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">+++ go.mod</span></span><br><span class="line">@@ 32,3 +32,5 @@</span><br><span class="line">    gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect</span><br><span class="line">    gopkg.in/yaml.v3 v3.0.1 // indirect</span><br><span class="line">)</span><br><span class="line"><span class="addition">+</span></span><br><span class="line"><span class="addition">+ replace go.bug.st/lsp =&gt; github.com/speelbarrow/go-lsp v0.1.3-0.20241103164431-cf1c00fb5806</span></span><br></pre></td></tr></table></figure></div></li><li><p>进一步查看其对于 <code>go-lsp</code> 所做的修改：</p>  <div class="code-container" data-rel="Diff"><figure class="iseeu highlight diff"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">--- jsonrpc/jsonrpc_connection.go</span></span><br><span class="line"><span class="comment">+++ jsonrpc/jsonrpc_connection.go</span></span><br><span class="line"><span class="meta">@@ -277,7 +277,7 @@</span> func (c *Connection) SendRequest(ctx context.Context, method string, params json</span><br><span class="line">        _, active := c.activeOutRequests[id]</span><br><span class="line">        c.activeOutRequestsMutex.Unlock()</span><br><span class="line">        if active &#123;</span><br><span class="line"><span class="deletion">-           if notif, err := json.Marshal(CancelParams&#123;ID: encodedId&#125;); err != nil &#123;</span></span><br><span class="line"><span class="addition">+           if notif, err := json.Marshal(CancelParams&#123;ID: encodedID&#125;); err != nil &#123;</span></span><br><span class="line">                       // should never happen</span><br><span class="line">                       panic(&quot;internal error: failed json encoding&quot;)</span><br><span class="line">                  &#125; else &#123;</span><br></pre></td></tr></table></figure></div></li><li><p>原来只是一个类型 typo 错误吗……</p></li></ul><h3 id="解决方案"><a href="#解决方案" class="headerlink" title="解决方案"></a>解决方案</h3><ul><li><p>虽然上文提到的 patch 已经能够使得 <code>arduino-language-server</code> 运行在 neovim v0.10+ 的版本中，但是该 PR 并未被 merge。查看 <code>go-lsp</code> 仓库的 Insights 界面可知：</p><p>  <img src="https://s2.loli.net/2025/02/22/1jNncSWpA6PZkLv.png" alt="screenshot_22022025_114218.jpg"></p></li><li><p><del>很显然这个仓库已经死了。</del></p></li><li><p>仔细看了一下，这个仓库似乎仅仅是为了 <code>arduino-language-server</code> 而 fork 的一个仓库，作者 <a class="link"   href="https://github.com/cmaglie" >cmaglie<i class="fa-solid fa-arrow-up-right ml-[0.2em] font-light align-text-top text-[0.7em] link-icon"></i></a> 已经很久没有对其进行维护了。</p></li></ul><div class="callout callout--titled warning mb-4 rounded-small shadow-redefine-flat bg-(--callout-bg-color) p-3 pl-1 relative flex flex-row gap-2"><div role="none" class="rounded-full self-stretch w-0.5 bg-(--callout-primary-color) shrink-0 opacity-60"></div><div class="flex flex-col gap-2"><div class="callout__title flex items-center gap-2 font-semibold tracking-tight"><i class="callout__icon fa-clock leading-none text-(--callout-primary-color) text-sm shrink-0"></i> 更新</div><div class="callout__content markdown-body flex-1 min-w-0"><p>cmaglie 于 2025-03-19 合并了该 PR，现在可以直接使用 Mason 安装的 arduino-language-server 了。</p></div></div></div><hr><ul><li><p>尽管如此，办法也还是有的：</p><ul><li><p>将 <a class="link"   href="https://github.com/speelbarrow/arduino-language-server/tree/main" >speelbarrow 的 repo<i class="fa-solid fa-arrow-up-right ml-[0.2em] font-light align-text-top text-[0.7em] link-icon"></i></a> 克隆到本地：</p>  <div class="code-container" data-rel="Bash"><figure class="iseeu highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git <span class="built_in">clone</span> https://github.com/speelbarrow/arduino-language-server.git</span><br></pre></td></tr></table></figure></div></li><li><p>手动 build 一个依赖正确 <code>go-lsp</code> 的 <code>arduino-language-server</code>：</p>  <div class="code-container" data-rel="Bash"><figure class="iseeu highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">cd</span> arduino-language-server &amp;&amp; go build</span><br></pre></td></tr></table></figure></div></li><li><p>将 Mason 里安装的可执行文件替换为手动构建的 <code>arduino-language-server</code>：</p>  <div class="code-container" data-rel="Bash"><figure class="iseeu highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">cp</span> ./arduino-language-server ~/.local/share/nvim/mason/packages/arduino-language-server/arduino-language-server</span><br></pre></td></tr></table></figure></div></li></ul></li></ul><h2 id="配置文件"><a href="#配置文件" class="headerlink" title="配置文件"></a>配置文件</h2><ul><li><p>顺带的，<code>arduino-language-server</code> 的启动需要一些前置步骤以及一些命令行参数。</p></li><li><p>前置步骤详见： <a class="link"   href="https://github.com/neovim/nvim-lspconfig/blob/master/doc/configs.md#arduino_language_server" >lspconfig doc<i class="fa-solid fa-arrow-up-right ml-[0.2em] font-light align-text-top text-[0.7em] link-icon"></i></a></p></li><li><p>本人开发 ESP32 CAM 所需要的命令行参数如下：</p><p>  <em>astrolsp.lua:</em></p>  <div class="code-container" data-rel="Lua"><figure class="iseeu highlight lua"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line">arduino_language_server = &#123;</span><br><span class="line">    on_new_config = <span class="function"><span class="keyword">function</span><span class="params">(config)</span></span></span><br><span class="line">        <span class="built_in">config</span>.capabilities.textDocument.semanticTokens = vim.NIL</span><br><span class="line">        <span class="built_in">config</span>.capabilities.workspace.semanticTokens = vim.NIL</span><br><span class="line">        <span class="built_in">config</span>.cmd = &#123;</span><br><span class="line">            <span class="string">&quot;arduino-language-server&quot;</span>,</span><br><span class="line">            <span class="string">&quot;-clangd&quot;</span>,</span><br><span class="line">            <span class="string">&quot;/home/last/.local/share/nvim/mason/bin/clangd&quot;</span>,</span><br><span class="line">            <span class="string">&quot;-cli&quot;</span>,</span><br><span class="line">            <span class="string">&quot;/usr/bin/arduino-cli&quot;</span>,</span><br><span class="line">            <span class="string">&quot;-cli-config&quot;</span>,</span><br><span class="line">            <span class="string">&quot;/home/last/.arduino15/arduino-cli.yaml&quot;</span>,</span><br><span class="line">            <span class="string">&quot;-fqbn&quot;</span>,</span><br><span class="line">            <span class="string">&quot;esp32:esp32:esp32cam&quot;</span>,</span><br><span class="line">        &#125;</span><br><span class="line">    <span class="keyword">end</span>,</span><br><span class="line">&#125;,</span><br></pre></td></tr></table></figure></div><p>  <em>注意：该配置仅适用于 AstroNvim 下，且需要使用 Mason 安装 <code>clangd</code></em></p></li></ul><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><ul><li>又是被环境配置折磨的一天。</li><li>这麻烦还是我自己找的，哈哈。</li></ul>]]>
    </content>
    <id>https://blog.imlast.top/2025/02/22/arduino-language-server-on-neovim/</id>
    <link href="https://blog.imlast.top/2025/02/22/arduino-language-server-on-neovim/"/>
    <published>2025-02-22T03:05:34.000Z</published>
    <summary>在 Neovim 中安装并使用 arduino-language-server 时所遇到的问题和解决方案</summary>
    <title>在 Neovim(v0.10+) 上使用 arduino-language-server</title>
    <updated>2025-02-22T03:05:34.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>Last</name>
    </author>
    <category term="笔记" scheme="https://blog.imlast.top/categories/%E7%AC%94%E8%AE%B0/"/>
    <category term="c" scheme="https://blog.imlast.top/tags/c/"/>
    <category term="pa" scheme="https://blog.imlast.top/tags/pa/"/>
    <content>
      <![CDATA[<div class="callout callout--titled warning mb-4 rounded-small shadow-redefine-flat bg-(--callout-bg-color) p-3 pl-1 relative flex flex-row gap-2"><div role="none" class="rounded-full self-stretch w-0.5 bg-(--callout-primary-color) shrink-0 opacity-60"></div><div class="flex flex-col gap-2"><div class="callout__title flex items-center gap-2 font-semibold tracking-tight"><i class="callout__icon fa-triangle-exclamation leading-none text-(--callout-primary-color) text-sm shrink-0"></i> Warning</div><div class="callout__content markdown-body flex-1 min-w-0"><p>If someone is reading this blog, please be aware that the writer <strong>DID NOT</strong> consider the experience of the other readers.<br>After all, the most important thing is about writing things down for better memorization.</p></div></div></div><h2 id="Exception-Handling-Interruption"><a href="#Exception-Handling-Interruption" class="headerlink" title="Exception Handling&#x2F;Interruption"></a>Exception Handling&#x2F;Interruption</h2><ul><li><p>riscv32 uses instruction <code>ecall</code> for self-trapping. Once the cpu recieved an <code>ecall</code> instruction, it would then store the current address pointed by <code>pc</code> to CSR <code>mepc</code> and push its status(context) on to the stack, and jump to an exception handler function.</p></li><li><p>This design is required because of privilege implementation. We want the application’s behavior to be limited, and let the OS do the ‘sensitive’ works, like file read&#x2F;write or memory allocation.</p></li><li><p>See also :</p><blockquote><p>riscv-privileged-20211203.pdf - page.37</p></blockquote></li></ul><h3 id="CSR-Control-and-Status-Register"><a href="#CSR-Control-and-Status-Register" class="headerlink" title="CSR: Control and Status Register"></a>CSR: Control and Status Register</h3><blockquote><p><strong>DeepSeek</strong>: CSRs are special registers in RISC-V processors used to control and monitor the CPU’s operation. They manage system-level functions such as interrupts, exceptions, and processor modes.</p></blockquote><ul><li>Basically, CSRs are registers which stores data related to the control and status of the CPU.</li></ul><hr><ul><li><p>In PA3, we would implement four CSRs for riscv32-nemu, which are:</p><ul><li><code>mepc</code> – <strong>Machine Exception Program Counter</strong>: Stores the address of the instruction where execution should resume after handling an exception or interrupt.</li><li><code>mcause</code> – <strong>Machine Cause Register</strong>: Indicates the reason for an exception or interrupt.</li><li><code>mstatus</code> – <strong>Machine Status Register</strong>: Tracks the processor’s current state, including interrupt enable bits and processor mode.</li><li><code>mtvec</code> – <strong>Machine Trap Vector Base Address Register</strong>: Holds the base address for the exception&#x2F;interruption handler.</li></ul></li><li><p>As we do not care about priviledge mode in PA, <code>mstatus</code> does not require much attention.</p></li><li><p>Also, <code>mtvec</code> is set to ‘direct mode’, which means that all exceptions jump to the same address, which is function <code>__am_asm_trap</code> which we set its address into <code>mtvec</code> in <code>Trap.S</code>(See down below).</p></li></ul><h3 id="Trap-S"><a href="#Trap-S" class="headerlink" title="Trap.S"></a><code>Trap.S</code></h3><ul><li><p>The error handler function is registerred to CSR <code>mtvec</code> when initializing cte module:</p>  <div class="code-container" data-rel="C"><figure class="iseeu highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">bool</span> <span class="title function_">cte_init</span><span class="params">(Context *(*handler)(Event, Context *))</span> &#123;</span><br><span class="line">    <span class="comment">// initialize exception entry</span></span><br><span class="line">    <span class="keyword">asm</span> <span class="title function_">volatile</span><span class="params">(<span class="string">&quot;csrw mtvec, %0&quot;</span> : : <span class="string">&quot;r&quot;</span>(__am_asm_trap))</span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// register event handler</span></span><br><span class="line">    user_handler = handler;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div></li><li><p>In this code snippet, function <code>__am_asm_trap</code> is defined in a file named <code>Trap.S</code>. The reason why it’s written in assembly is that it is an architecture specific script, which means that the way the emulated cpu stores and restores context varies as the architecture varies, and that requires completely differenct implementation.</p></li><li><p>Content in <code>Trap.S</code>:</p>  <div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br></pre></td><td class="code"><pre><span class="line">.align 3</span><br><span class="line">.globl __am_asm_trap</span><br><span class="line">__am_asm_trap:</span><br><span class="line">addi sp, sp, -CONTEXT_SIZE</span><br><span class="line"></span><br><span class="line">MAP(REGS, PUSH)</span><br><span class="line"></span><br><span class="line">csrr t0, mcause</span><br><span class="line">csrr t1, mstatus</span><br><span class="line">csrr t2, mepc</span><br><span class="line"></span><br><span class="line">STORE t0, OFFSET_CAUSE(sp)</span><br><span class="line">STORE t1, OFFSET_STATUS(sp)</span><br><span class="line">STORE t2, OFFSET_EPC(sp)</span><br><span class="line"></span><br><span class="line"># set mstatus.MPRV to pass difftest</span><br><span class="line">li a0, (1 &lt;&lt; 17)</span><br><span class="line">or t1, t1, a0</span><br><span class="line">csrw mstatus, t1</span><br><span class="line"></span><br><span class="line">mv a0, sp</span><br><span class="line">jal __am_irq_handle</span><br><span class="line"></span><br><span class="line">LOAD t1, OFFSET_STATUS(sp)</span><br><span class="line">LOAD t2, OFFSET_EPC(sp)</span><br><span class="line">csrw mstatus, t1</span><br><span class="line">csrw mepc, t2</span><br><span class="line"></span><br><span class="line">MAP(REGS, POP)</span><br><span class="line"></span><br><span class="line">addi sp, sp, CONTEXT_SIZE</span><br><span class="line">mret</span><br></pre></td></tr></table></figure></div></li><li><p>Reading this script really cost me a lot of time, mainly due to its frequent usage of macros.</p></li><li><p>The main behavior of this script is:</p><ul><li>First store all of the data in the registers on stack</li><li>Set CSR registers(<code>mcause</code>, <code>mstatus</code>, <code>mepc</code>)</li><li>Jump to interruption handling function <code>__am_irq_handle</code></li><li>After the handler function returns, restore the registers using the data previously stored on stack</li></ul></li></ul><h3 id="System-Call"><a href="#System-Call" class="headerlink" title="System Call"></a>System Call</h3><ul><li><p>When the application need something to be done by the OS, it would invoke a ‘system call’, which is based on the exception handling&#x2F;interruption mechanism mentioned above.</p></li><li><p>See <code>syscall.c</code> in navy-apps:</p>  <div class="code-container" data-rel="C"><figure class="iseeu highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">intptr_t</span> _syscall_(<span class="type">intptr_t</span> type, <span class="type">intptr_t</span> a0, <span class="type">intptr_t</span> a1, <span class="type">intptr_t</span> a2) &#123;</span><br><span class="line"><span class="keyword">register</span> <span class="type">intptr_t</span> _gpr1 <span class="title function_">asm</span><span class="params">(<span class="string">&quot;a7&quot;</span>)</span> = type;</span><br><span class="line"><span class="keyword">register</span> <span class="type">intptr_t</span> _gpr2 <span class="title function_">asm</span><span class="params">(<span class="string">&quot;a0&quot;</span>)</span> = a0;</span><br><span class="line"><span class="keyword">register</span> <span class="type">intptr_t</span> _gpr3 <span class="title function_">asm</span><span class="params">(<span class="string">&quot;a1&quot;</span>)</span> = a1;</span><br><span class="line"><span class="keyword">register</span> <span class="type">intptr_t</span> _gpr4 <span class="title function_">asm</span><span class="params">(<span class="string">&quot;a2&quot;</span>)</span> = a2;</span><br><span class="line"><span class="keyword">register</span> <span class="type">intptr_t</span> ret <span class="title function_">asm</span><span class="params">(<span class="string">&quot;a0&quot;</span>)</span>;</span><br><span class="line"><span class="keyword">asm</span> <span class="title function_">volatile</span><span class="params">(<span class="string">&quot;ecall&quot;</span></span></span><br><span class="line"><span class="params">            : <span class="string">&quot;=r&quot;</span>(ret)</span></span><br><span class="line"><span class="params">            : <span class="string">&quot;r&quot;</span>(_gpr1), <span class="string">&quot;r&quot;</span>(_gpr2), <span class="string">&quot;r&quot;</span>(_gpr3), <span class="string">&quot;r&quot;</span>(_gpr4))</span>;</span><br><span class="line"><span class="keyword">return</span> ret;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div><p>  <em>(All macros are expanded)</em></p><ul><li><p>Here we can see that a system call is basically executing the <code>ecall</code> instruction plus passing some related data to certain registers, in order to tell the OS what the application requires it to do.</p></li><li><p>The registers taken by riscv32 in an exception handling process are <code>a7</code>, <code>a0</code>, <code>a1</code> and <code>a2</code>.(Specifically, <code>a7</code> register for storing system call type) After the system call is done, the return value is passed to the OS through register <code>a0</code>, and then returned to the user program.</p></li></ul></li></ul><h3 id="The-Entire-Process-of-a-System-Call"><a href="#The-Entire-Process-of-a-System-Call" class="headerlink" title="The Entire Process of a System Call"></a>The Entire Process of a System Call</h3><ul><li>User program invokes a system call, which is by executing assemble instruction <code>ecall</code>.</li><li>NEMU interpret <code>ecall</code> and call function <code>isa_raise_intr</code></li><li><code>isa_raise_intr</code> set <code>mepc</code>, <code>mcause</code> and returns address of the handler function, which is <code>am_irq_handle</code>, NEMU then jump to it.</li><li><code>am_irq_handle</code> create an <code>Event</code> object with the exception NO stored in register <code>a7</code> and pass it to <code>user_handler</code>(if there is one).</li><li>After the user handler function returns, the <code>Context</code> object will be returned in order to restore the context.</li><li>User program resumes.</li></ul><pre class="mermaid">flowchart TD    A[syscall] --> B[isa_raise_intr]    B --> C[__am_asm_trap]    C --> D[__am_irq_handle]    D --> E[user_handler]    E --> F[restore context & resume program]</pre><h2 id="Multi-elf-Support-for-Ftrace"><a href="#Multi-elf-Support-for-Ftrace" class="headerlink" title="Multi-elf Support for Ftrace"></a>Multi-elf Support for Ftrace</h2><h3 id="Implementation"><a href="#Implementation" class="headerlink" title="Implementation"></a>Implementation</h3><ul><li>Since we already have a single-elf ftrace working, changing the variable holding the file path into a linked list would solve the problem.</li></ul><h3 id="start-issue"><a href="#start-issue" class="headerlink" title="_start issue"></a><code>_start</code> issue</h3><ul><li><p>There’s a function called <code>_start</code> in a linking script, located at <code>/libs/libos/src/crt0/start.S</code>. It is not labeled as a function and its size is default to 0.</p>  <div class="code-container" data-rel="C"><figure class="iseeu highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">for</span> (<span class="type">size_t</span> i = <span class="number">0</span>; i &lt; func_table.size; i++) &#123;</span><br><span class="line">    FuncSym curr_func = func_table.funcs[i];</span><br><span class="line">    <span class="keyword">if</span> (addr &gt;= curr_func.value &amp;&amp;</span><br><span class="line">        addr &lt; curr_func.value + curr_func.size) &#123;</span><br><span class="line">        <span class="keyword">return</span> curr_func;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div></li><li><p>From the code in <code>ftrace</code> we can see that, the jump address must fall into the range of <code>[value, value + size)</code>. While the size of <code>_start</code> is defaulted to 0, ftrace can not find the corresponding symbol when the program invokes <code>_start</code>.</p></li></ul><h2 id="5000-Bytes-8-0K"><a href="#5000-Bytes-8-0K" class="headerlink" title="5000 Bytes == 8.0K ?"></a><code>5000 Bytes == 8.0K</code> ?</h2><ul><li><p>When I was checking the size of the file being taken into <code>fsimg</code>, I encountered a strange thing that:</p>  <div class="code-container" data-rel="Bash"><figure class="iseeu highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">$ <span class="built_in">du</span> -h fsimg/share/files/num</span><br><span class="line">8.0K    fsimg/share/files/num</span><br><span class="line"></span><br><span class="line">$ <span class="built_in">du</span> --bytes fsimg/share/files/num</span><br><span class="line">5000    fsimg/share/files/num</span><br></pre></td></tr></table></figure></div><ul><li><code>du -h</code> shows a <code>8.0K</code> file</li><li><code>du --bytes</code> shows a 5000-byte file</li></ul></li><li><p>The reason is that, <strong>5000 bytes</strong> is the actual content size of the file, while <strong>8.0K</strong> is the disk usage of it.</p></li><li><p>Filesystems allocate space in fixed-size blocks, often 4096 bytes(4K) each. A 5000-byte file needs two blocks <em>(2 x 4096 &#x3D; 8192 bytes)</em>, rounded to 8.0K.</p></li></ul><h2 id="sbrk-Implementation"><a href="#sbrk-Implementation" class="headerlink" title="sbrk Implementation"></a><code>sbrk</code> Implementation</h2><ul><li><p><code>man sbrk</code>:</p><blockquote><p><code>brk()</code> and <code>sbrk()</code> change the location of the program break, which defines the end of the process’s data segment (i.e., the program break is the first location after the end of the uninitialized data segment <em>(.bss)</em> ).<br>Increasing the program break has the effect of allocating memory to the process; decreasing the break deallocates memory.<br>…<br><code>sbrk()</code> increments the program’s data space by <em>increment</em> bytes. Calling <code>sbrk()</code> with an <em>increment</em> of 0 can be used to find the current location of the program break.</p></blockquote></li><li><p>We will need a symbol <code>end</code> to get the initial memory address of <em>program break</em>.</p></li><li><p><code>man end</code>:</p><blockquote><p><strong>end</strong>: This is the first address past the end of the uninitialized data segment (also known as the BSS segment)</p></blockquote><ul><li><p>We can get the address this way:</p>  <div class="code-container" data-rel="C"><figure class="iseeu highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;stdio.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;stdlib.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="keyword">extern</span> <span class="type">char</span> end; <span class="comment">/* The symbol must have some type,</span></span><br><span class="line"><span class="comment">                    or &quot;gcc -Wall&quot; complains */</span></span><br><span class="line"></span><br><span class="line"><span class="type">int</span> <span class="title function_">main</span><span class="params">(<span class="type">void</span>)</span> &#123;</span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">&quot;First address past:\n&quot;</span>);</span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">&quot;    uninitialized data (end)  %10p\n&quot;</span>, &amp;end);</span><br><span class="line"></span><br><span class="line">    <span class="built_in">exit</span>(EXIT_SUCCESS);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div></li></ul></li></ul><hr><ul><li><p>When <code>sbrk</code> is invoked by lib functions, it would invoke a system call called <code>SYS_brk</code>, which is basically requesting the OS for <code>increment</code> bytes of memory section.</p></li><li><p>As nanos-lite is now only a single-program OS, all of the free memory is allocatable to the program, so the system call handler in nanos-lite for <code>SYS_brk</code> would just return 0 for now.</p></li><li><p>As for <code>syscall.c</code> in navy-apps, <code>_sbrk</code> need to maintain the program break. Specifically, it is required to update pbreak at each time it is invoked, and return the pointer to the old program break.</p></li></ul><h2 id="File-System"><a href="#File-System" class="headerlink" title="File System"></a>File System</h2><ul><li><p>File system in nanos-lite is a simplified version of real-world fs. Specifically:</p><ul><li>The size of each file is fixed</li><li>Data written into the file is not allowed to exceed its initial limit</li><li>The number of files is fixed, meaning that we can not create new files</li><li>There’s no directoy</li></ul></li><li><p>These design makes the implementation of file system for nanos-lite much more simpler and easier.</p></li></ul><hr><ul><li><p>The abstraction of the files is an array which contains all of the file names stored in <code>fsimg</code>. <em>(file names contains the path)</em></p></li><li><p>The way to achieve this is by executing the script written in <code>Makefile</code> along with some preset files such as <code>stdin</code> &amp; <code>stdout</code>.</p></li><li><p>The file list array is defined in <code>fs.c</code>:</p>  <div class="code-container" data-rel="C"><figure class="iseeu highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">typedef</span> <span class="class"><span class="keyword">struct</span> &#123;</span></span><br><span class="line">    <span class="type">char</span> *name;</span><br><span class="line">    <span class="type">size_t</span> size;</span><br><span class="line">    <span class="type">size_t</span> disk_offset;</span><br><span class="line">    ReadFn read;</span><br><span class="line">    WriteFn write;</span><br><span class="line">&#125; Finfo;</span><br><span class="line"></span><br><span class="line"><span class="comment">/* This is the information about all files in disk. */</span></span><br><span class="line"><span class="type">static</span> Finfo file_table[] __attribute__((used)) = &#123;</span><br><span class="line">    [FD_STDIN] = &#123;<span class="string">&quot;stdin&quot;</span>, <span class="number">-1</span>, <span class="number">0</span>, invalid_read, invalid_write&#125;,</span><br><span class="line">    [FD_STDOUT] = &#123;<span class="string">&quot;stdout&quot;</span>, <span class="number">-1</span>, <span class="number">0</span>, invalid_read, serial_write&#125;,</span><br><span class="line">    [FD_STDERR] = &#123;<span class="string">&quot;stderr&quot;</span>, <span class="number">-1</span>, <span class="number">0</span>, invalid_read, serial_write&#125;,</span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&quot;files.h&quot;</span></span></span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure></div><ul><li><p><code>files.h</code> is generated by navy-apps’s Makefile:</p>  <div class="code-container" data-rel="Makefile"><figure class="iseeu highlight makefile"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="variable">$(RAMDISK)</span>: fsimg</span><br><span class="line">    <span class="variable">$(<span class="built_in">eval</span> FSIMG_FILES := $(<span class="built_in">shell</span> find -L ./fsimg -type f)</span>)</span><br><span class="line">    @mkdir -p $(@D)</span><br><span class="line">    @cat <span class="variable">$(FSIMG_FILES)</span> &gt; <span class="variable">$@</span></span><br><span class="line">    @truncate -s \%512 <span class="variable">$@</span></span><br><span class="line">    @echo <span class="string">&quot;// file path, file size, offset in disk&quot;</span> &gt; <span class="variable">$(RAMDISK_H)</span></span><br><span class="line">    @wc -c <span class="variable">$(FSIMG_FILES)</span></span><br><span class="line">        | grep -v &#x27;total$$&#x27;</span><br><span class="line">        | sed -e &#x27;s+ ./fsimg+ +&#x27;</span><br><span class="line">        | awk -v sum=0 &#x27;&#123;print <span class="string">&quot;\x7b\x22&quot;</span> $$2 <span class="string">&quot;\x22\x2c &quot;</span> $$1 <span class="string">&quot;\x2c &quot;</span> sum <span class="string">&quot;\x7d\x2c&quot;</span>;sum += $$1&#125;&#x27; &gt;&gt; <span class="variable">$(RAMDISK_H)</span></span><br></pre></td></tr></table></figure></div></li></ul></li><li><p>The last two element of <code>Finfo</code> is the ‘read’ and ‘write’ function for that specific file.</p></li></ul><hr><ul><li><p>Noted that, we treat <code>stdin</code>, <code>stdout</code> and <code>stderr</code> as files. This is what the statement ‘Everything is a file’ in Linux means: <strong>the system components – including hardware devices, directories, and processes – are represented as files in the filesystem.</strong></p></li><li><p>This design simplifies interaction with system resources because they can be accessed, read, and written using standard file operations (e.g., <code>open</code>, <code>read</code>, <code>write</code>).</p></li><li><p>With a function pointer set to each file’s struct object, we would only need to call it in <code>fs_read</code> &amp; <code>fs_write</code>.</p></li></ul><h2 id="Compound-Literal-Issue"><a href="#Compound-Literal-Issue" class="headerlink" title="Compound Literal Issue"></a>Compound Literal Issue</h2><ul><li><p>When I was implementing some SDL apis, I encountered yet another subtle issue: defining and retreating the pointer of a compound literal struct object.</p></li><li><p>Here’s the initial code:</p>  <div class="code-container" data-rel="C"><figure class="iseeu highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">if</span> (srcrect == <span class="literal">NULL</span>)</span><br><span class="line">    srcrect = &amp;((SDL_Rect)&#123;.x = <span class="number">0</span>, .y = <span class="number">0</span>, .w = src-&gt;w, .h = src-&gt;h&#125;);</span><br></pre></td></tr></table></figure></div></li><li><p>Turns out that <code>(SDL_Rect)&#123;...&#125;</code> creates a temporary SDL_Rect object <strong>inside the if block</strong>. After the if statement ends, the temporary object disappears (goes out of scope).</p></li><li><p>As a result, <code>srcrect</code> now holds a dangling pointer (an invalid memory address). Undefined behavior occurs when you try to use srcrect.</p></li></ul><hr><ul><li><p>To fix this, we would need a persistent object:</p>  <div class="code-container" data-rel="C"><figure class="iseeu highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">SDL_Rect full_src = &#123;.x = <span class="number">0</span>, .y = <span class="number">0</span>, .w = src-&gt;w, .h = src-&gt;h&#125;;</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> (!srcrect) srcrect = &amp;full_src;</span><br></pre></td></tr></table></figure></div></li><li><p>This way, <code>full_src</code> is defined in the function’s scope, so it will live until the function returns.</p></li></ul><h2 id="Pixel-Value-Conversion-Palette"><a href="#Pixel-Value-Conversion-Palette" class="headerlink" title="Pixel Value Conversion: Palette"></a>Pixel Value Conversion: Palette</h2><ul><li><p>In PAL, the value of each pixel is 8-bit in length, and is not representing the color of that pixel. Instead, it is an index to a palette. We would need to convert that index to a 32-bit color value when rendering the rectangle.</p></li><li><p>One thing worth mentioning is that, <code>SDL_Color-&gt;val</code> is not the target color value. The format of it does not match <code>AARRGGBB</code>, so we’ll have to manually set the bit of the pixel data:</p>  <div class="code-container" data-rel="C"><figure class="iseeu highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">0</span>; i &lt; h; i++) &#123;</span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> j = <span class="number">0</span>; j &lt; w; j++) &#123;</span><br><span class="line">        <span class="type">uint8_t</span> idx = read_ptr[j];</span><br><span class="line">        buf[i * w + j] = (colors[idx].a &lt;&lt; <span class="number">24</span>) | (colors[idx].r &lt;&lt; <span class="number">16</span>) |</span><br><span class="line">                            (colors[idx].g &lt;&lt; <span class="number">8</span>) | (colors[idx].b);</span><br><span class="line">    &#125;</span><br><span class="line">    read_ptr += s-&gt;pitch;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div></li></ul><h2 id="A-Way-of-Passing-Graphic-Parameter-Through-Standard-File-Operation"><a href="#A-Way-of-Passing-Graphic-Parameter-Through-Standard-File-Operation" class="headerlink" title="A Way of Passing Graphic Parameter Through Standard File Operation"></a>A Way of Passing Graphic Parameter Through Standard File Operation</h2><ul><li><p>In NDL, the api responsible for drawing rectangle is like this:</p>  <div class="code-container" data-rel="C"><figure class="iseeu highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">void</span> <span class="title function_">NDL_DrawRect</span><span class="params">(<span class="type">uint32_t</span> *pixels, <span class="type">int</span> x, <span class="type">int</span> y, <span class="type">int</span> w, <span class="type">int</span> h)</span>;</span><br></pre></td></tr></table></figure></div></li><li><p>This brings up an issue: <code>write</code> only accept three paramters, while the first of them is the file descriptor. That means, we would need to pass four parameters to a function which could only accept two parameters.</p></li><li><p>There’re two ways to solve this that I came up with:</p><ol><li>invoke <code>write</code> for each line of the rectangle</li><li>write all of the empty pixels as well</li></ol></li><li><p>After asking Deepseek, it recommended another way of achieving this: <strong>store <code>x</code> and <code>y</code> in the first 8 bytes of <code>pixels</code>.</strong></p></li><li><p>Though the top-left corner of the image presented might be a little bit stange, the trade-off for better performance is worthy.</p></li></ul><hr><ul><li>The most affected application is <code>typing-game</code>, cuz the program would draw a rectangle for each dropping letter.</li></ul><h2 id="To-Be-Continued…"><a href="#To-Be-Continued…" class="headerlink" title="To Be Continued…"></a>To Be Continued…</h2><div class="callout callout--titled info mb-4 rounded-small shadow-redefine-flat bg-(--callout-bg-color) p-3 pl-1 relative flex flex-row gap-2"><div role="none" class="rounded-full self-stretch w-0.5 bg-(--callout-primary-color) shrink-0 opacity-60"></div><div class="flex flex-col gap-2"><div class="callout__title flex items-center gap-2 font-semibold tracking-tight"><i class="callout__icon fa-face-sad-cry leading-none text-(--callout-primary-color) text-sm shrink-0"></i> Suspend</div><div class="callout__content markdown-body flex-1 min-w-0"><p>Gotta prepare for the master entrance examination :(</p></div></div></div>]]>
    </content>
    <id>https://blog.imlast.top/2025/02/10/nju-pa-3/</id>
    <link href="https://blog.imlast.top/2025/02/10/nju-pa-3/"/>
    <published>2025-02-10T12:00:00.000Z</published>
    <summary>exception handling, system calls, file system, and directmedia layer implementation</summary>
    <title>PA3 - Batch Processing System</title>
    <updated>2025-02-10T12:00:00.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>Last</name>
    </author>
    <category term="随笔" scheme="https://blog.imlast.top/categories/%E9%9A%8F%E7%AC%94/"/>
    <content>
      <![CDATA[<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><ul><li>2024 就这么结束了。我的心中一片茫然。</li><li>时间既没有停歇也没有加速，是我的感官模糊了，它们被分散到了其他地方。</li><li>只要目光不盯紧钟表的指针，它们就疯狂地转动不停。</li></ul><h2 id="2024-年"><a href="#2024-年" class="headerlink" title="2024 年"></a>2024 年</h2><h3 id="硬件更新"><a href="#硬件更新" class="headerlink" title="硬件更新"></a>硬件更新</h3><h4 id="手机"><a href="#手机" class="headerlink" title="手机"></a>手机</h4><ul><li><p>我这用了四年的 iPhone 11 终于是可以退役了。经过接连几次的系统更新之后他已经卡的让人无法接受了。</p></li><li><p>本人此前用过的两部手机都是 iPhone（8，11），对于苹果已经积攒了十足的怨气。其在硬件上的吝啬，技术上的不思进取和坐地起价的蛮横态度属实让我气愤不已的同时感到匪夷所思。类似于 NFC 和屏下指纹这样简单好用的功能迟迟不出，高分和高刷屏被价格高昂的 pro 型号独占，简直是把“赚钱”二字写在脸上了。</p></li><li><p>被其粉丝吹捧的 IOS 系统更是让我感到恶心不已。究其原因，以下几点：</p><ul><li>系统封闭。想安装什么软件还得看 App Store 里有没有。侧载安装每周都需要链接电脑刷新签名，无言了。</li><li>卡顿。四年前该手机刚入手的时候系统流畅度确实无可挑剔，然而在接连几次系统更新之后不知怎的越来越卡。对于计划报废这些说辞我不置可否，但是卡顿的系统着实让我感到非常恶心。</li><li>微信同步消息简直慢的让人不可置信。每次打开微信都需要等待 5s 以上的时间等待消息同步，我简直无法理解。询问旁人后得知，这就是 IOS 独有的问题。</li></ul></li><li><p>如今终于可以摆脱这恶心的品牌，快哉快哉！</p></li></ul><hr><ul><li>新手机是一部 One Plus Ace3v。由于我不需要玩手游也没有拍照需求，对于手机的要求仅仅就是个即时通讯器而已。再加之对于苹果长久以来各种毛病的怨气，我选择了一款价格便宜的安卓系统手机。</li><li>从 5 月份一直用到现在，说实话体验相当良好。2k 屏，高刷，NFC，屏下指纹以及应用分身。这些在苹果那里倘若不买价格 1w+ 的 pro 型号，通通没有。</li></ul><hr><ul><li><p>当然，购买该平牌手机还有一个原因就是 root 方便，甚至还有一加盒子这样的社区工具，好评。</p></li><li><p>依稀记得本人一开始 root 的时候一股脑安装了许多模块，大概是其中某个模块发生问题，导致手机无法开机，也就是俗称的“变砖”。好在提前安装了一个“自动救砖”的模块，重启三次之后自动隔离了其他模块，算是有惊无险。</p></li><li><p>说实在的，本人对于刷 root 权限这件事其实也没有那么执着，仅仅是抱着“玩一玩”的心态去尝试的。对于各家厂商纷纷停止向用户开放 root 权限，我其实是可以理解的。毕竟，开屏广告没了的话，广告收入会削减不少。以及所谓的“用户数据也是手机厂商宝贵的资产”。</p></li><li><p>当然，理解归理解，该骂还是要骂。资本的最终目的还是为了增值。</p></li></ul><h4 id="电脑"><a href="#电脑" class="headerlink" title="电脑"></a>电脑</h4><ul><li><p>五月份又拜托 Jazz 哥，在旧台式的基础上翻新配置组了台新机器。配置是 AMD R7 5700x 和 AMD 6750 GRE 12g，迈出了逃离 N 卡的第一步，好！也是得益于此，后续我再次 <a href="https://blog.imlast.top/2024/12/18/2nd-hyprland/">折腾 Hyprland</a> 的体验好了不少，也是给我用上 Hyprland 了（喜）。</p></li><li><p>当然，选择 AMD 的考量主要还是在于性价比。N 卡借助人工智能领域飞黄腾达之后，连带着价格也跟着水涨船高。在我发现一张 N 卡的价格要占到整机预算的 50% 以上之后，就再也不想考虑 N 卡了。</p></li><li><p>换了台式之后最大的感受不是性能的提升，而是骤减的散热压力。再也不会像曾今用笔记本时那样，cpu 动不动就涨到 90°C 了。</p></li><li><p>顺带的换了个鼠标。随便挑了一个国产的牌子，不曾想该鼠标续航能力简直惊人——充一次电能用将近两个月，属实令我惊讶不已。</p></li></ul><h4 id="Neo-Ergo"><a href="#Neo-Ergo" class="headerlink" title="Neo Ergo"></a>Neo Ergo</h4><ul><li>购买了一把 Alice 布局的客制化键盘。阳极银、紫镜面配重。ALU 定位板，无绵 Gasket。轴体选了紫薇轴，我很喜欢这种脆响的声音。键帽是随便选的一套植物园配色。</li></ul><p><img src="https://s2.loli.net/2025/01/01/rQSlRhGBPKeaDkN.jpg" alt="neo-ergo.jpg"></p><ul><li>使用体验相当优秀，声音也很令我满意。把底绵和夹心绵卸下之后，声音的脆响程度又上了一层楼，甚好，甚好。</li></ul><h3 id="开源之夏"><a href="#开源之夏" class="headerlink" title="开源之夏"></a>开源之夏</h3><ul><li>今年暑假参加了<a class="link"   href="https://summer-ospp.ac.cn/" >开源之夏<i class="fa-solid fa-arrow-up-right ml-[0.2em] font-light align-text-top text-[0.7em] link-icon"></i></a>（OSPP）活动，具体内容是为 <a class="link"   href="https://github.com/emqx/MQTTX" >MQTTX<i class="fa-solid fa-arrow-up-right ml-[0.2em] font-light align-text-top text-[0.7em] link-icon"></i></a> 添加 AVRO 编码支持。</li><li>说实话，在挑选项目的时候我对自己有些过于不自信了，这个项目对我而言似乎是有点太过简单了……总共花在上面的时间恐怕不足三个星期，其中的大部分时间还都是花在沟通上，技术上可以说没遇到任何难题。</li><li>项目导师可以说十分尽职尽责，事无巨细的解答了我对于项目许多的问题：<a href="https://blog.imlast.top/tags/mqttx/">相关博客</a></li><li>开发工作完成后导师还邀请我加入了 GitHub 上 emqx 的组织，可惜此后我拿不出那么多时间就继续开发该项目，总感觉有些对不起导师。不过在如今潦草的经济大环境下，他应该能理解我。</li></ul><h3 id="Hyprland"><a href="#Hyprland" class="headerlink" title="Hyprland"></a>Hyprland</h3><ul><li>也是给我用上 Hyprland 了。经过重重险阻解决了大部分 BUG，详见 <a href="https://blog.imlast.top/2024/12/18/2nd-hyprland/">这篇博客</a>。</li></ul><p><img src="https://s2.loli.net/2024/12/29/kI57gHsNOZinFcu.png" alt="screenshot_29122024_052933.jpg"></p><h3 id="南京大学-ICS"><a href="#南京大学-ICS" class="headerlink" title="南京大学 ICS"></a>南京大学 ICS</h3><ul><li><p>上半年学期末考试之前，因为懒得复习所以找了点别的事情干。依稀记起此前群友有推荐过这个课程，便找来讲义想着体验一番。 哪成想一下陷入其中，断断续续的一直写到了 PA3。</p></li><li><p>这门课可以说是刚好处在我能力边缘的位置——努努力就可以达到其中的要求，在这个过程中还能学到不少计算机系统的运作原理。长时间阅读文档和 DE 后终于调通了某个功能之后，那种喜悦能让我感觉自己至今为止的努力都没有白费。</p></li><li><p>虽然用了一种很“俗套”的说法，但是那一瞬间的感触确实如是。</p></li></ul><hr><ul><li>完成 PA2 结尾处选做任务声卡后录的纪念视频：</li></ul><iframe width="100%" height="468" src="//player.bilibili.com/player.html?isOutside=true&aid=113527444544674&bvid=BV1KtBBYXETA&cid=26903250566&p=1&autoplay=0" scrolling="no" border="0" frameborder="no" framespacing="0" allowfullscreen="true"></iframe><h3 id="服务器"><a href="#服务器" class="headerlink" title="服务器"></a>服务器</h3><ul><li>去年租的服务器到期了，第二年续期的费用居然高达接近 1k。记得当时租用一年的费用也不过 100 左右，果然优惠只有新客户才能享受，老客户和狗不得入内。</li><li>在各种主机类的博文中翻来找去，找到一个野草云的半价优惠码。于是浏览了一遍各型号的配置，租了一台 2C4G 的 HK 服务器，费用 ¥150 &#x2F; 年。</li></ul><hr><ul><li>由于懒惰和拖延，直到前一个服务器临近过期我才开始迁移上面的服务和配置。由于匆忙，遗漏了许多配置文件，最终这些破事全都加倍返还了回来……</li><li>另外不知为何，部署在这台服务器上的 Twikoo 后端导致博文中加载评论奇慢无比。但是我已经不愿意再为此付出时间精力了，能用就行吧。</li></ul><h3 id="学校"><a href="#学校" class="headerlink" title="学校"></a>学校</h3><h4 id="辅修专业的毕业设计"><a href="#辅修专业的毕业设计" class="headerlink" title="辅修专业的毕业设计"></a>辅修专业的毕业设计</h4><ul><li>24 年上半年，辅修计算机专业迎来了最后的毕业设计环节。</li><li>分配给我的指导老师实在是让我感觉无语至极，其漫不经心的态度和极差的沟通能力让我数次接近于失去耐心。整个毕业设计的项目源码总共就花了不到两个星期的时间，后面由于沟通不畅导致格式问题愣是修改了一个多月。</li><li>论文工作和家事交织在一起，这个两个月耗干了我所有的心性和精力。我不认为在这种情况下我还能拿出复习考研的精力了，那时候我只想着休息。</li><li>和指导老师沟通后，他为我宽限了一些论文的截止日期。看在这份上，我还是配合了他的工作和修改意见，尽管数次被气的喘不上气。</li></ul><h4 id="恼人的论文课"><a href="#恼人的论文课" class="headerlink" title="恼人的论文课"></a>恼人的论文课</h4><ul><li><p>主专业论文开题在即，或许是为此前被退回的论文感到警醒，学校安排所有学院都添加了一门论文课。</p></li><li><p>我们学院安排了一位经管专业的晋洪涛老师为我们授课。实话实说，这门课简直是一坨屎：</p><ul><li><p>前四周的课都在讲如何为论文拟题，最后发现论文的题目是由指导老师拟定的。</p></li><li><p>所有的范例和论文结构讲解都只适用于经管领域的论文，与我们信管可以说是毫不相干，毫无帮助。</p></li><li><p>该老师简直是精神病，排在大四——许多同学都有升学和实习安排的时期——的课程，愣是想尽办法的提升出勤率：</p><ul><li>旷课代价是平时分 -30，连请假都要扣分</li><li>每节课都有不定次数，不定时间的签到</li><li>每次签到都附带题目，甚至有用于验证的题目</li><li>连已经出国留学（预科）的同学他都不予容忍，硬是逼得同学回国来上课</li></ul></li><li><p>该课程的作业和期末考核可以说是毫无意义：</p><ul><li>论文的具体内容涉及计算机领域的知识，这位经管出身的老师显然是无能为力。</li><li>计算机专业的毕业设计论文结构基本固定，完全不需要加以额外指导。</li></ul></li><li><p><em>虽然现在还没到学期末，不过可想而知最终的期末成绩不可能太好看。</em></p></li></ul></li></ul><hr><ul><li><p>综上所述，我完全无法理解这门课的意义何在——该课程既没有提供有价值的知识，反而还极为注重诸如考勤和作业格式之类的形式要求。</p></li><li><p>学期中的时候我便实在无法忍受，去找辅导员攀谈，得到的结果也仅仅是“可以向学校提出意见”。</p></li><li><p>这位老师让我想起在 <a class="link"   href="https://survivesjtu.gitbook.io/survivesjtumanual" >上海交通大学生存手册<i class="fa-solid fa-arrow-up-right ml-[0.2em] font-light align-text-top text-[0.7em] link-icon"></i></a> 看到的一则观点：</p><blockquote><p>…… 我们关注是否点名，重点应该看的是这个老师面对学生的态度。有的老师睁一只眼闭一只眼，即使不来上课，也不会太为难学生。**而另外一些比较自卑的老师却把学生不来上课当成对自己的奇耻大辱，甚至会想出课前课后点两次名外加数人头这样的手段防止学生翘课。**其实，如果老师想不计代价查出谁没来上课，这真的是太简单的一件事情。</p></blockquote></li><li><p>这位晋洪涛老师算是让我见识到什么才是大学里最畜生、最恶心的老师，令人作呕。</p></li></ul><h4 id="小组作业"><a href="#小组作业" class="headerlink" title="小组作业"></a>小组作业</h4><ul><li><p>最后一学期的一门信息系统课程，其主要的课程目标是安排我们以小班（25 人左右）为单位，其中每个小班分为 6 个小组，设计并开发一个完整的网页应用。</p></li><li><p>且不提这门课程提供的分工方式如何逆天，我想，只要是经历过小组作业的人都能预想到这将会是怎样的“盛况”，更不要提在我们这个不学无术的学校和学院。</p></li><li><p>同级其他班的绝大部分小组都选择了外包，对此我丝毫没有感到意外。 真正令我感到瞠目结舌的是，居然有人连外包都整不明白？而且很不幸，这一组人就出现在我们小班之中，担任着前端开发的要职。</p></li></ul><hr><ul><li><p>这一组的同学先是外包了一份和第一组递交的设计稿毫不相关的前端网页，在后续的小组提醒之后他们才又递交了一份新的。然而，这份新的前端网页也同样和设计稿毫无交集……</p></li><li><p>就这么扯皮到了 Milestone 2 的提交节点，后续流程中的小组不得以将情况告知了课程老师，最终老师的方案是额外给我们班开个线上会议帮助解决进度问题。</p></li><li><p>谁成想，前端组居然干脆直接缺席线上会议，而且还缺席了两次，其中第二次还有人专门去通知其组长开会。最后外教老师不得以在线下授课期间为了我们小班单独开会解决问题，会上后三组的同学连外教老师说的话都听不懂，这给予了我极大的震撼……</p></li><li><p>最终，我帮助他们拟定了开发计划和时间节点，外教老师为我们小班延期了 2 周 ddl。</p></li><li><p>毫不意外的，最后他们做了一坨屎出来。前端组找的外包用现成的博客框架改了一个网站出来，简直蠢的让人无话可说。更不要提后端开发组使用了已经老掉牙的 PHP 开发，对此我已经无话可说了。</p></li><li><p>早早完成任务的第一组中一位已经保研的同学还曾忧心忡忡的来询问我具体的进度，说这是他第一次担心会在学校课程中挂科。这太荒唐了，简直令我捧腹大笑。</p></li></ul><hr><ul><li>说真的，这个项目可以说是一点难度都没有。现在互联网应用的开发门槛已经非常低了，毫不夸张的说，让我独自一人开发都要不了两个星期。</li><li>至于我的同学们是如何落得如此地步，我只能说可能这就是普通高校的现状吧。</li></ul><hr><ul><li>在我还住在学校里的时候，前端组的组长住在隔壁宿舍。我对他的印象基本上就是一个家境富裕但生活“难以自理”的家伙。</li><li>依稀记得在大一的时候曾在某门通识课就和他在同一组，大一的他和现在可以说是同一副德行：那时候他忙着物色女孩，小组作业分工到他头上的工作一直到截止之前都未能完成。至于这一次他到底在忙些什么，我就无从得知了。</li><li>我没法理解是什么经历促成了他这样对他人漠不关心的性格，以及对学习接近于零的热情。对于他我已经不想说什么“应试教育摧毁了学生的精神”之类的话了，或许他在其他的领域能够有所建树吧。</li></ul><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><ul><li><p>2024 年就像一阵风一样转瞬即逝，正如同在去年的年终总结中写下的， <em>对于时光荏苒的感慨都已经是家常便饭了</em> 。</p></li><li><p>2024 是苦难和解脱的一年，布满了独属于我这个年龄与这个时代的焦虑和迷茫。</p></li><li><p>在这样荒蛮又虚无的世界里，我要成为谁？</p></li></ul>]]>
    </content>
    <id>https://blog.imlast.top/2024/12/30/2024-Summary/</id>
    <link href="https://blog.imlast.top/2024/12/30/2024-Summary/"/>
    <published>2024-12-30T14:17:00.000Z</published>
    <summary>在这稳步下沉的大船上度过又一个荒废的地球年</summary>
    <title>2024 年终总结</title>
    <updated>2024-12-30T14:17:00.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>Last</name>
    </author>
    <category term="开发记录" scheme="https://blog.imlast.top/categories/%E5%BC%80%E5%8F%91%E8%AE%B0%E5%BD%95/"/>
    <category term="python" scheme="https://blog.imlast.top/tags/python/"/>
    <category term="selenium" scheme="https://blog.imlast.top/tags/selenium/"/>
    <category term="web crawler" scheme="https://blog.imlast.top/tags/web-crawler/"/>
    <category term="netease music" scheme="https://blog.imlast.top/tags/netease-music/"/>
    <content>
      <![CDATA[<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><ul><li><p>今日突然在一个 A 科群里发现群友正在用一个机器人玩猜歌名的游戏(Arcaea 开字母)，如下图：</p><p><img src="https://s2.loli.net/2024/12/21/dtQi3T1UjWlXv6b.png" alt="screenshot_21122024_030422.jpg"></p></li><li><p>本古董 A 科玩家已经退坑了四年，最近回坑看着这多出来的几百首新曲实在是无能为力<del>（南村群童欺我老无力）</del>，于是想着能不能写一个 Python 程序帮助我匹配。（绝不是想要作弊</p></li></ul><h2 id="正文"><a href="#正文" class="headerlink" title="正文"></a>正文</h2><h3 id="爬虫"><a href="#爬虫" class="headerlink" title="爬虫"></a>爬虫</h3><ul><li><p>想要做到匹配曲名，首先当然是需要有一个完整的曲名库作为数据源。我首先想到的方法是爬取 <a class="link"   href="https://arcaea.fandom.com/wiki/Songs_by_Date" >Arcaea Fandom - Songs by Date<i class="fa-solid fa-arrow-up-right ml-[0.2em] font-light align-text-top text-[0.7em] link-icon"></i></a> 页面中的曲名。尝试后发现，该页面的 HTML 组织稍有混乱，难以匹配所有的曲目。</p></li><li><p>具体来说，该页面中包含了曲目名称的 HTML 元素是这样的：</p><div class="code-container" data-rel="Html"><figure class="iseeu highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">a</span> <span class="attr">href</span>=<span class="string">&quot;/wiki/Memory_Forest&quot;</span> <span class="attr">title</span>=<span class="string">&quot;Memory Forest&quot;</span>&gt;</span>Memory Forest<span class="tag">&lt;/<span class="name">a</span>&gt;</span></span><br></pre></td></tr></table></figure></div><ul><li><p>于是我尝试用以下代码来匹配它：</p><div class="code-container" data-rel="Python"><figure class="iseeu highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">a_tags = soup.find_all(<span class="string">&quot;a&quot;</span>, href=re.<span class="built_in">compile</span>(<span class="string">r&quot;^/wiki/&quot;</span>), title=<span class="literal">True</span>)</span><br><span class="line"><span class="keyword">for</span> a_tag <span class="keyword">in</span> a_tags:</span><br><span class="line">    title_value = a_tag[<span class="string">&quot;title&quot;</span>]</span><br><span class="line">    song_title = a_tag.text.strip()</span><br><span class="line">    <span class="keyword">if</span> (</span><br><span class="line">        song_title.lower() == title_value.lower()</span><br><span class="line">        <span class="keyword">and</span> song_title <span class="keyword">not</span> <span class="keyword">in</span> false_titles</span><br><span class="line">        <span class="keyword">and</span> song_title <span class="keyword">not</span> <span class="keyword">in</span> songs</span><br><span class="line">    ):</span><br><span class="line">        songs.append(a_tag.text)</span><br></pre></td></tr></table></figure></div></li><li><p>很快我就发现，有些曲目的 HTML 源码并不能被匹配到，例如：</p><div class="code-container" data-rel="Html"><figure class="iseeu highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">a</span> <span class="attr">href</span>=<span class="string">&quot;/wiki/%CE%9C&quot;</span> <span class="attr">title</span>=<span class="string">&quot;Μ&quot;</span>&gt;</span>µ<span class="tag">&lt;/<span class="name">a</span>&gt;</span></span><br><span class="line">...</span><br><span class="line"><span class="tag">&lt;<span class="name">a</span> <span class="attr">href</span>=<span class="string">&quot;/wiki/Genesis_(Iris)&quot;</span> <span class="attr">title</span>=<span class="string">&quot;Genesis (Iris)&quot;</span>&gt;</span>Genesis<span class="tag">&lt;/<span class="name">a</span>&gt;</span></span><br></pre></td></tr></table></figure></div></li><li><p>实际爬取到的曲目数量仅有 424 首，与实际的 465 首相差甚远。</p></li></ul></li></ul><details class="relative my-4 border border-border-color bg-second-background-color rounded-md  blue" data-header-exclude><summary class="px-4 py-2 rounded-md shadow-[0_0_2px_0_var(--shadow-color-1)] cursor-pointer not-markdown"><i class="fa-solid fa-chevron-right"></i>climb_fandom</summary><div class="content p-4 "><div class="code-container" data-rel="Python"><figure class="iseeu highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> re</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> requests</span><br><span class="line"><span class="keyword">from</span> bs4 <span class="keyword">import</span> BeautifulSoup</span><br><span class="line"></span><br><span class="line">url = <span class="string">&quot;https://arcaea.fandom.com/wiki/Songs_by_Date#List_of_songs&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># Custom headers to mimic a browser</span></span><br><span class="line">headers = &#123;</span><br><span class="line">    <span class="string">&quot;User-Agent&quot;</span>: (</span><br><span class="line">        <span class="string">&quot;Mozilla/5.0 (Windows NT 10.0; Win64; x64) &quot;</span></span><br><span class="line">        <span class="string">&quot;AppleWebKit/537.36 (KHTML, like Gecko) &quot;</span></span><br><span class="line">        <span class="string">&quot;Chrome/114.0.0.0 Safari/537.36&quot;</span></span><br><span class="line">    ),</span><br><span class="line">    <span class="string">&quot;Accept-Language&quot;</span>: <span class="string">&quot;en-US,en;q=0.9&quot;</span>,</span><br><span class="line">    <span class="string">&quot;Referer&quot;</span>: <span class="string">&quot;https://www.google.com&quot;</span>,</span><br><span class="line">&#125;</span><br><span class="line">false_titles = [<span class="string">&quot;Songs by Pack&quot;</span>, <span class="string">&quot;Songs by Level&quot;</span>]</span><br><span class="line">missing_titles = [<span class="string">&quot;Genesis&quot;</span>, <span class="string">&quot;μ&quot;</span>]</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">main</span>() -&gt; <span class="literal">None</span>:</span><br><span class="line">    songs = []</span><br><span class="line"></span><br><span class="line">    <span class="keyword">try</span>:</span><br><span class="line">        <span class="comment"># Request the URL with headers</span></span><br><span class="line">        response = requests.get(url, headers=headers)</span><br><span class="line">        response.raise_for_status()  <span class="comment"># Raise an exception for HTTP errors</span></span><br><span class="line"></span><br><span class="line">        <span class="comment"># Parse the HTML content</span></span><br><span class="line">        soup = BeautifulSoup(response.text, <span class="string">&quot;html.parser&quot;</span>)</span><br><span class="line"></span><br><span class="line">        <span class="comment"># Find all &lt;a&gt; elements and extract their content</span></span><br><span class="line">        a_tags = soup.find_all(<span class="string">&quot;a&quot;</span>, href=re.<span class="built_in">compile</span>(<span class="string">r&quot;^/wiki/&quot;</span>), title=<span class="literal">True</span>)</span><br><span class="line">        <span class="keyword">for</span> a_tag <span class="keyword">in</span> a_tags:</span><br><span class="line">            title_value = a_tag[<span class="string">&quot;title&quot;</span>]</span><br><span class="line">            song_title = a_tag.text.strip()</span><br><span class="line">            <span class="keyword">if</span> (</span><br><span class="line">                song_title.lower() == title_value.lower()</span><br><span class="line">                <span class="keyword">and</span> song_title <span class="keyword">not</span> <span class="keyword">in</span> false_titles</span><br><span class="line">                <span class="keyword">and</span> song_title <span class="keyword">not</span> <span class="keyword">in</span> songs</span><br><span class="line">            ):</span><br><span class="line">                songs.append(a_tag.text)</span><br><span class="line"></span><br><span class="line">        <span class="keyword">for</span> t <span class="keyword">in</span> missing_titles:</span><br><span class="line">            songs.append(t)</span><br><span class="line"></span><br><span class="line">        <span class="built_in">print</span>(</span><br><span class="line">            <span class="string">f&quot;Climb complete. Grabbed <span class="subst">&#123;<span class="built_in">len</span>(songs)&#125;</span> songs. This may include false title.&quot;</span></span><br><span class="line">        )</span><br><span class="line">        <span class="keyword">with</span> <span class="built_in">open</span>(<span class="string">&quot;songs.txt&quot;</span>, <span class="string">&quot;w&quot;</span>) <span class="keyword">as</span> file:</span><br><span class="line">            file.write(<span class="string">&quot;\n&quot;</span>.join(songs))</span><br><span class="line"></span><br><span class="line">    <span class="keyword">except</span> requests.exceptions.RequestException <span class="keyword">as</span> e:</span><br><span class="line">        <span class="built_in">print</span>(<span class="string">&quot;An error occurred while requesting the URL:&quot;</span>, e)</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> __name__ == <span class="string">&quot;__main__&quot;</span>:</span><br><span class="line">    main()</span><br></pre></td></tr></table></figure></div><p>可以看到，其中定义了列表 <code>false_titles</code> 和 <code>missing_titles</code> 来手动剔除错误的匹配结果以及添加缺少的结果。</p></div></details><hr><ul><li><p>然后我突然想起来，网易云上有个 <a class="link"   href="https://music.163.com/#/djradio?id=350746079" >Arcaea 电台<i class="fa-solid fa-arrow-up-right ml-[0.2em] font-light align-text-top text-[0.7em] link-icon"></i></a>。</p></li><li><p>不同于一般网站，网易云不知用什么方法使得其音乐列表无法被 requests 库获取，无法使用一般的 <code>bs4</code> &amp; <code>requests</code> 经典组合爬取。猜测是使用了网页脚本刷新内容。</p></li><li><p>本人倒是在很久以前写过的一个网易云爬虫，使用的是 <a class="link"   href="https://github.com/p697/cloudmusic" >cloudmusic<i class="fa-solid fa-arrow-up-right ml-[0.2em] font-light align-text-top text-[0.7em] link-icon"></i></a> 库。该库已经很久没人维护和使用了，而且似乎也不支持电台查询。</p></li><li><p>于是我便谷歌了一下网易云的爬虫方案，找到了一个使用 <a class="link"   href="https://www.selenium.dev/" >selenium<i class="fa-solid fa-arrow-up-right ml-[0.2em] font-light align-text-top text-[0.7em] link-icon"></i></a> 自动化测试框架模拟浏览器的方法：</p><div class="code-container" data-rel="Python"><figure class="iseeu highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">from</span> random <span class="keyword">import</span> randint</span><br><span class="line"></span><br><span class="line"><span class="keyword">from</span> selenium <span class="keyword">import</span> webdriver</span><br><span class="line"><span class="keyword">from</span> selenium.webdriver.chrome.options <span class="keyword">import</span> Options</span><br><span class="line"><span class="keyword">from</span> selenium.webdriver.common.by <span class="keyword">import</span> By</span><br><span class="line"></span><br><span class="line">base_url = <span class="string">&quot;https://music.163.com/#/djradio?id=350746079&amp;order=1&amp;_hash=programlist&amp;limit=100&amp;offset=&quot;</span></span><br><span class="line">output_file = <span class="string">&quot;./songs.txt&quot;</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">main</span>():</span><br><span class="line">    songs = []</span><br><span class="line"></span><br><span class="line">    chrome_options = Options()</span><br><span class="line"></span><br><span class="line">    <span class="comment"># run without gui window</span></span><br><span class="line">    chrome_options.add_argument(<span class="string">&quot;--headless&quot;</span>)</span><br><span class="line">    browser = webdriver.Chrome(chrome_options)</span><br><span class="line">    <span class="comment"># browser.maximize_window()</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">for</span> page <span class="keyword">in</span> <span class="built_in">range</span>(<span class="number">5</span>):</span><br><span class="line">        <span class="built_in">print</span>(<span class="string">f&quot;Grabbing from page <span class="subst">&#123;page + <span class="number">1</span>&#125;</span>...&quot;</span>)</span><br><span class="line"></span><br><span class="line">        <span class="comment"># open radio page</span></span><br><span class="line">        browser.get(base_url + <span class="built_in">str</span>(page * <span class="number">100</span>))</span><br><span class="line"></span><br><span class="line">        <span class="comment"># wait for a random length of period</span></span><br><span class="line">        rand_wait = randint(<span class="number">3</span>, <span class="number">6</span>)</span><br><span class="line">        <span class="built_in">print</span>(<span class="string">f&quot;waiting for <span class="subst">&#123;rand_wait&#125;</span> secs...&quot;</span>)</span><br><span class="line">        browser.implicitly_wait(rand_wait)</span><br><span class="line"></span><br><span class="line">        browser.switch_to.frame(<span class="string">&quot;contentFrame&quot;</span>)</span><br><span class="line"></span><br><span class="line">        song_list = browser.find_elements(</span><br><span class="line">            By.XPATH, <span class="string">&#x27;//*[contains(@href, &quot;program?id=&quot;)]&#x27;</span></span><br><span class="line">        )</span><br><span class="line"></span><br><span class="line">        <span class="keyword">for</span> song <span class="keyword">in</span> song_list:</span><br><span class="line">            <span class="keyword">if</span> song.text <span class="keyword">not</span> <span class="keyword">in</span> songs:</span><br><span class="line">                songs.append(song.text)</span><br><span class="line"></span><br><span class="line">    <span class="built_in">print</span>(<span class="string">f&quot;Writing to file: <span class="subst">&#123;output_file&#125;</span>...&quot;</span>)</span><br><span class="line">    <span class="keyword">with</span> <span class="built_in">open</span>(output_file, <span class="string">&quot;w&quot;</span>) <span class="keyword">as</span> file:</span><br><span class="line">        file.write(<span class="string">&quot;\n&quot;</span>.join(songs))</span><br><span class="line">    <span class="built_in">print</span>(<span class="string">&quot;Done.&quot;</span>)</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> __name__ == <span class="string">&quot;__main__&quot;</span>:</span><br><span class="line">    main()</span><br></pre></td></tr></table></figure></div></li><li><p>其中，电台内每个曲目的 HTML 源码如下所示：</p><div class="code-container" data-rel="Html"><figure class="iseeu highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">a</span> <span class="attr">href</span>=<span class="string">&quot;/program?id=1368523734&quot;</span> <span class="attr">title</span>=<span class="string">&quot;Memory Forest&quot;</span>&gt;</span>Memory Forest<span class="tag">&lt;/<span class="name">a</span>&gt;</span></span><br></pre></td></tr></table></figure></div></li><li><p>此处可采用如下方式进行匹配：</p><div class="code-container" data-rel="Python"><figure class="iseeu highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">song_list = browser.find_elements(</span><br><span class="line">    By.XPATH, <span class="string">&#x27;//*[contains(@href, &quot;program?id=&quot;)]&#x27;</span></span><br><span class="line">)</span><br></pre></td></tr></table></figure></div></li><li><p>经简单观察和测试，并未发现多余的结果。</p></li></ul><h3 id="模式匹配"><a href="#模式匹配" class="headerlink" title="模式匹配"></a>模式匹配</h3><ul><li><p>来自群机器人的题目形式如同 <code>?e?e?i?</code>(Genesis)。</p></li><li><p>在 ChatGPT 的帮助下快速的完成了如下的代码：</p><div class="code-container" data-rel="Python"><figure class="iseeu highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> argparse</span><br><span class="line"><span class="keyword">import</span> re</span><br><span class="line"></span><br><span class="line"><span class="keyword">from</span> colorama <span class="keyword">import</span> Fore, Style, init</span><br><span class="line"></span><br><span class="line">DEFAULT_FILE_PATH = <span class="string">&quot;./songs.txt&quot;</span></span><br><span class="line">all_songs = []</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">init_songs</span>(<span class="params">file_path</span>) -&gt; <span class="literal">None</span>:</span><br><span class="line">    <span class="keyword">with</span> <span class="built_in">open</span>(file_path, <span class="string">&quot;r&quot;</span>) <span class="keyword">as</span> file:</span><br><span class="line">        <span class="keyword">for</span> line <span class="keyword">in</span> file:</span><br><span class="line">            all_songs.append(line.strip())</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">match_pattern</span>(<span class="params">pattern: <span class="built_in">str</span></span>) -&gt; <span class="literal">None</span>:</span><br><span class="line">    match_list = []</span><br><span class="line">    <span class="keyword">try</span>:</span><br><span class="line">        regex = re.<span class="built_in">compile</span>(pattern.replace(<span class="string">&quot;?&quot;</span>, <span class="string">&quot;.&quot;</span>))</span><br><span class="line">    <span class="keyword">except</span> Exception <span class="keyword">as</span> e:</span><br><span class="line">        <span class="built_in">print</span>(Fore.RED + <span class="string">&quot;Unable to parse pattern.&quot;</span> + Style.RESET_ALL)</span><br><span class="line">        <span class="keyword">return</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">for</span> s <span class="keyword">in</span> all_songs:</span><br><span class="line">        <span class="keyword">if</span> regex.fullmatch(s):</span><br><span class="line">            match_list.append(s)</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> <span class="built_in">len</span>(match_list) == <span class="number">0</span>:</span><br><span class="line">        <span class="built_in">print</span>(Fore.RED + <span class="string">&quot;No result found.&quot;</span> + Style.RESET_ALL)</span><br><span class="line">        <span class="keyword">return</span></span><br><span class="line"></span><br><span class="line">    matched_num = <span class="built_in">len</span>(match_list)</span><br><span class="line">    <span class="comment"># found too much songs, ask user whether to display or not</span></span><br><span class="line">    <span class="keyword">if</span> matched_num &gt;= <span class="number">10</span>:</span><br><span class="line">        <span class="built_in">print</span>(</span><br><span class="line">            Fore.LIGHTYELLOW_EX</span><br><span class="line">            + <span class="string">f&quot;Found <span class="subst">&#123;matched_num&#125;</span>(&gt;= 10) songs, display all? (y/N)&quot;</span></span><br><span class="line">        )</span><br><span class="line">        choice = <span class="built_in">input</span>()</span><br><span class="line">        <span class="keyword">if</span> choice.lower() != <span class="string">&quot;y&quot;</span>:</span><br><span class="line">            <span class="built_in">print</span>(<span class="string">&quot;Skipped.&quot;</span>)</span><br><span class="line">            <span class="keyword">return</span></span><br><span class="line"></span><br><span class="line">    <span class="built_in">print</span>(Fore.GREEN + <span class="string">f&quot;Matched <span class="subst">&#123;<span class="built_in">len</span>(match_list)&#125;</span> song(s):&quot;</span> + Style.RESET_ALL)</span><br><span class="line">    <span class="keyword">for</span> t <span class="keyword">in</span> match_list:</span><br><span class="line">        <span class="built_in">print</span>(t)</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">main</span>() -&gt; <span class="literal">None</span>:</span><br><span class="line">    <span class="comment"># colorama</span></span><br><span class="line">    init()</span><br><span class="line"></span><br><span class="line">    parser = argparse.ArgumentParser(description=<span class="string">&quot;Process args.&quot;</span>)</span><br><span class="line">    parser.add_argument(</span><br><span class="line">        <span class="string">&quot;-f&quot;</span>,</span><br><span class="line">        <span class="string">&quot;--songs-file&quot;</span>,</span><br><span class="line">        <span class="built_in">type</span>=<span class="built_in">str</span>,</span><br><span class="line">        default=DEFAULT_FILE_PATH,</span><br><span class="line">        <span class="built_in">help</span>=<span class="string">&quot;File contains all songs of Arcaea&quot;</span>,</span><br><span class="line">    )</span><br><span class="line">    parser.add_argument(<span class="string">&quot;-b&quot;</span>, <span class="string">&quot;--batch&quot;</span>, action=<span class="string">&quot;store_true&quot;</span>, <span class="built_in">help</span>=<span class="string">&quot;Run in batch mode&quot;</span>)</span><br><span class="line"></span><br><span class="line">    args = parser.parse_args()</span><br><span class="line"></span><br><span class="line">    <span class="comment"># read songs from file</span></span><br><span class="line">    <span class="built_in">print</span>(</span><br><span class="line">        Fore.LIGHTMAGENTA_EX</span><br><span class="line">        + <span class="string">f&quot;Using <span class="subst">&#123;args.songs_file&#125;</span> as song list.&quot;</span></span><br><span class="line">        + Style.RESET_ALL</span><br><span class="line">    )</span><br><span class="line">    init_songs(args.songs_file)</span><br><span class="line">    <span class="keyword">if</span> <span class="built_in">len</span>(all_songs) &lt;= <span class="number">400</span>:</span><br><span class="line">        <span class="built_in">print</span>(</span><br><span class="line">            Fore.RED</span><br><span class="line">            + <span class="string">&quot;Initialization Failed. Too few songs.(&lt;= 400, should be around 450)&quot;</span></span><br><span class="line">            + Style.RESET_ALL</span><br><span class="line">        )</span><br><span class="line">        <span class="keyword">return</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> args.batch:</span><br><span class="line">        <span class="comment"># <span class="doctag">TODO:</span> batch mode</span></span><br><span class="line">        <span class="built_in">print</span>(Fore.RED + <span class="string">&quot;Batch mode is not supported yet.&quot;</span> + Style.RESET_ALL)</span><br><span class="line">        <span class="keyword">return</span></span><br><span class="line"></span><br><span class="line">    <span class="built_in">print</span>(Fore.YELLOW + <span class="string">&quot;\033[1mInput pattern to start match.&quot;</span> + Style.RESET_ALL)</span><br><span class="line">    <span class="built_in">print</span>(Fore.LIGHTCYAN_EX + <span class="string">&quot;Pattern is case sensitive.&quot;</span> + Style.RESET_ALL)</span><br><span class="line">    <span class="built_in">print</span>(</span><br><span class="line">        Fore.LIGHTCYAN_EX</span><br><span class="line">        + <span class="string">&quot;Pattern ends with `+` indicates an uncertain length.&quot;</span></span><br><span class="line">        + Style.RESET_ALL</span><br><span class="line">    )</span><br><span class="line">    <span class="built_in">print</span>(<span class="string">&quot;Type Q/q to quit.&quot;</span>)</span><br><span class="line"></span><br><span class="line">    <span class="keyword">while</span> <span class="literal">True</span>:</span><br><span class="line">        usr_input = <span class="built_in">input</span>()</span><br><span class="line">        <span class="keyword">if</span> usr_input.lower() == <span class="string">&quot;q&quot;</span>:</span><br><span class="line">            <span class="built_in">print</span>(<span class="string">&quot;Goodbye.&quot;</span>)</span><br><span class="line">            <span class="keyword">break</span></span><br><span class="line"></span><br><span class="line">        pattern = usr_input</span><br><span class="line">        <span class="keyword">if</span> usr_input[-<span class="number">1</span>] == <span class="string">&quot;+&quot;</span>:</span><br><span class="line">            pattern = pattern[:-<span class="number">1</span>] + <span class="string">&quot;.*&quot;</span></span><br><span class="line"></span><br><span class="line">        match_pattern(pattern)</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> __name__ == <span class="string">&quot;__main__&quot;</span>:</span><br><span class="line">    main()</span><br></pre></td></tr></table></figure></div></li><li><p>程序启动后进入一个循环中，持续接受用户输入并与文件中的曲名作匹配。</p></li><li><p>如果用户输入的模式串以 <code>+</code> 结束，则在模式串最后添加一个 <code>.*</code> 来匹配不定长度的曲名。</p></li></ul><div class="callout callout--titled info mb-4 rounded-small shadow-redefine-flat bg-(--callout-bg-color) p-3 pl-1 relative flex flex-row gap-2"><div role="none" class="rounded-full self-stretch w-0.5 bg-(--callout-primary-color) shrink-0 opacity-60"></div><div class="flex flex-col gap-2"><div class="callout__title flex items-center gap-2 font-semibold tracking-tight"><i class="callout__icon fa-code leading-none text-(--callout-primary-color) text-sm shrink-0"></i> Batch_Mode</div><div class="callout__content markdown-body flex-1 min-w-0"><p>可以看到，代码中存在一个未完成的 <code>batch mode</code>。<br>我本来是打算将他用于批量匹配曲名的，但是刚开始实现不久我就发现，在 Hyprland 环境下我的 linuxqq-nt-bwrap 无法将其中的内容复制到系统的粘贴板上。但是我却能够将系统粘贴板中的东西粘贴到 qq 里，颇为神奇。。</p></div></div></div><h2 id="结束"><a href="#结束" class="headerlink" title="结束"></a>结束</h2><ul><li>写的很爽，但是为此熬夜这么晚似乎不是很值得。</li></ul>]]>
    </content>
    <id>https://blog.imlast.top/2024/12/20/arcaea-song-matcher/</id>
    <link href="https://blog.imlast.top/2024/12/20/arcaea-song-matcher/"/>
    <published>2024-12-20T19:00:14.000Z</published>
    <summary>用 Python 写了个用来辅助猜曲名的匹配器，以及网易云电台爬虫</summary>
    <title>Arcaea 曲名匹配器</title>
    <updated>2024-12-20T19:00:14.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>Last</name>
    </author>
    <category term="笔记" scheme="https://blog.imlast.top/categories/%E7%AC%94%E8%AE%B0/"/>
    <category term="c" scheme="https://blog.imlast.top/tags/c/"/>
    <category term="pa" scheme="https://blog.imlast.top/tags/pa/"/>
    <content>
      <![CDATA[<div class="callout callout--titled warning mb-4 rounded-small shadow-redefine-flat bg-(--callout-bg-color) p-3 pl-1 relative flex flex-row gap-2"><div role="none" class="rounded-full self-stretch w-0.5 bg-(--callout-primary-color) shrink-0 opacity-60"></div><div class="flex flex-col gap-2"><div class="callout__title flex items-center gap-2 font-semibold tracking-tight"><i class="callout__icon fa-triangle-exclamation leading-none text-(--callout-primary-color) text-sm shrink-0"></i> Warning</div><div class="callout__content markdown-body flex-1 min-w-0"><p>If someone is reading this blog, please be aware that the writer <strong>DID NOT</strong> consider the experience of the other readers.<br>After all, the most important thing is about writing things down for better memorization.</p></div></div></div><h2 id="Compilation-Process"><a href="#Compilation-Process" class="headerlink" title="Compilation Process"></a>Compilation Process</h2><ul><li><p>We’ve already seen this before in class 1:</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">.c --precompile--&gt; .i --compile--&gt; .s --assemble--&gt; .o --link--&gt; .out</span><br></pre></td></tr></table></figure></div></li><li><p>Today’s lesson would be focusing on the process where the object file(.o) being transformed into an executable(.out).</p></li></ul><h2 id="Static-Linking"><a href="#Static-Linking" class="headerlink" title="Static Linking"></a>Static Linking</h2><h3 id="Introduction"><a href="#Introduction" class="headerlink" title="Introduction"></a>Introduction</h3><ul><li>The process of linking is basically connecting all of the required parts of a program together.</li><li>Say we are using a function from another file in the <code>main.c</code>, the compiler would not know which address to jump to unless we link the other file with <code>main.c</code> together.</li></ul><hr><ul><li><p>For example, if we have these three source files:</p><div class="code-container" data-rel="C"><figure class="iseeu highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// a.c</span></span><br><span class="line"><span class="type">int</span> <span class="title function_">foo</span> <span class="params">(<span class="type">int</span> a, <span class="type">int</span> b)</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> a + b;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// b.c</span></span><br><span class="line"><span class="type">int</span> x = <span class="number">100</span>, y = <span class="number">200</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// main.c</span></span><br><span class="line"><span class="keyword">extern</span> <span class="type">int</span> x, y;</span><br><span class="line"><span class="type">int</span> <span class="title function_">foo</span><span class="params">(<span class="type">int</span> a, <span class="type">int</span> b)</span>;</span><br><span class="line"><span class="type">int</span> <span class="title function_">main</span><span class="params">()</span> &#123;</span><br><span class="line">    foo(x, y);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div></li><li><p>And then compile them separately, we would get three object files: <code>a.o</code>, <code>b.o</code> and <code>main.o</code>.</p></li><li><p>Then we will have to link them together to get the final expected executable file:</p><div class="code-container" data-rel="C"><figure class="iseeu highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">gcc -<span class="type">static</span> a.o b.o main.o</span><br></pre></td></tr></table></figure></div><p><em>Note: the flag <code>-static</code> is to explicitly tell gcc to use static linking, as modern versions of gcc would use dynamic linking by default.</em></p></li><li><p>The linking process would fail if we are missing one of the object files:</p><div class="code-container" data-rel="Bash"><figure class="iseeu highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"> gcc -static b.o main.o</span><br><span class="line">/usr/bin/ld: main.o: <span class="keyword">in</span> <span class="keyword">function</span> `main<span class="string">&#x27;:</span></span><br><span class="line"><span class="string">main.c:(.text.startup+0x11): undefined reference to `foo&#x27;</span></span><br><span class="line">collect2: error: ld returned 1 <span class="built_in">exit</span> status</span><br></pre></td></tr></table></figure></div></li><li><p>Without linking, the compiler would never know where the definitions of <code>foo</code> or <code>x</code> <code>y</code> are.</p></li><li><p>But How exactly does the compiler do this?</p></li></ul><h3 id="Know-How"><a href="#Know-How" class="headerlink" title="Know How"></a>Know How</h3><ul><li>Turns out the object files compiled from source files are a type of ELF file called <strong>Relocatable File</strong>. This type of file contains code and data, and could be used for linking.</li></ul><table><thead><tr><th>ELF type</th><th>Explanation</th><th>Example(s)</th></tr></thead><tbody><tr><td>Relocatable File</td><td>Object files that can be combined with other object files during linking to create an executable or shared object</td><td><code>.o</code>, <code>.obj</code></td></tr><tr><td>Executable File</td><td>Files that contain a program ready to be executed directly by the system</td><td><code>.exe</code>, regular Linux programs</td></tr><tr><td>Shared Object File</td><td>Libraries that can be loaded and linked dynamically at runtim by multiple programs</td><td><code>.so</code>, <code>.dll</code></td></tr><tr><td>Core Dump File</td><td>Files containing a snapshot of a program’s memory when it crashes or terminates abnormally</td><td><code>core.1234</code> (<code>1234</code> for the PID)</td></tr></tbody></table><ul><li>Part of the ELF structure looks like this:</li></ul><table><thead><tr><th>Section</th><th>Explanation</th></tr></thead><tbody><tr><td>ELF_Header</td><td>Contains ident_num, version, machine type, entry point, etc.</td></tr><tr><td>.text section</td><td>Contains executable code&#x2F;instructions of the program in machine code format</td></tr><tr><td>.rodata section</td><td>Contains read-only data like string literals, constants and static lookup tables</td></tr><tr><td>.data section</td><td>Contains initialized global and static variables that can be modified at runtime</td></tr><tr><td>.bss section</td><td>Contains uninitialized global and static variables (zeroed at program start)</td></tr><tr><td>…</td><td>…</td></tr></tbody></table><p><em>Note: modern compiler would put variables initialized with value <code>0</code> into .bss section rather than .data section by default</em><br><em>Note: We can use <code>objdump -h</code> to see those sections’ info.</em></p><ul><li>For the structure of an ELF file, also see <a href="https://blog.imlast.top/2024/10/02/nju-pa-2/#Parsing-ELF">this chapter</a>.</li></ul><hr><ul><li>In the linking process, the compiler would join the different parts of those ELF files together to form a bigger file as the executable file. The differenct sections of those object files are merged.</li><li>This process includes two main phases: <strong>Symbol Resolution</strong> and <strong>Relocation</strong>.</li></ul><h4 id="Symbol-Resolution"><a href="#Symbol-Resolution" class="headerlink" title="Symbol Resolution"></a>Symbol Resolution</h4><ul><li><p>When source files are compiled into object files separately, the name of the functions and variables declared in the file are stored as <strong>symbols</strong>.</p></li><li><p>Each object file has a symbol table containing:</p><ul><li>Defined symbols (functions&#x2F;variables defined in the source file being compiled)</li><li>undefined symbols (functions&#x2F;variables defined elsewhere)</li></ul></li><li><p>During linking, the linker would scan all of the object files to match the defined and undefined symbols as much as possible.</p></li><li><p>In the example above, we have function <code>foo</code> and variable <code>x</code> &amp; <code>y</code> defined in other source files other than <code>main.c</code>. It is the linker which put them together to form a complete executable file that include all of those symbols and their definitions.</p></li></ul><h4 id="Relocation"><a href="#Relocation" class="headerlink" title="Relocation"></a>Relocation</h4><ul><li><p>Initially, each object files’s code is written assuming it starts at address 0.</p></li><li><p>The linker would:</p><ol><li>Assign final memory locations to all sections(.text, .data, etc)</li><li>Adjust all memory references to use these new addresses</li><li>Update the machine code with correct addresses</li></ol></li><li><p>Take the example above:</p><div class="code-container" data-rel="Bash"><figure class="iseeu highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"> objdump -d main.o</span><br><span class="line"></span><br><span class="line">main.o:     file format elf64-x86-64</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">Disassembly of section .text.startup.main:</span><br><span class="line"></span><br><span class="line">0000000000000000 &lt;main&gt;:</span><br><span class="line">0:    48 83 ec 08             sub    <span class="variable">$0x8</span>,%rsp</span><br><span class="line">4:    8b 35 00 00 00 00       mov    0x0(%rip),%esi        <span class="comment"># a &lt;main+0xa&gt;</span></span><br><span class="line">a:    8b 3d 00 00 00 00       mov    0x0(%rip),%edi        <span class="comment"># 10 &lt;main+0x10&gt;</span></span><br><span class="line">10:   e8 00 00 00 00          call   15 &lt;main+0x15&gt;</span><br><span class="line">15:   31 c0                   xor    %eax,%eax</span><br><span class="line">17:   48 83 c4 08             add    <span class="variable">$0x8</span>,%rsp</span><br><span class="line">1b:   c3                      ret</span><br></pre></td></tr></table></figure></div><ul><li><p>Notice the zeros in the address references:</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">10:   e8 00 00 00 00          call   15 &lt;main+0x15&gt;</span><br></pre></td></tr></table></figure></div></li><li><p>This is where the relocation placeholder is. This blank address will be filled in when linking happens.</p></li><li><p>Also, notice the instructions before <code>call</code>:</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">4:    8b 35 00 00 00 00       mov    0x0(%rip),%esi        # a &lt;main+0xa&gt;</span><br><span class="line">a:    8b 3d 00 00 00 00       mov    0x0(%rip),%edi        # 10 &lt;main+0x10&gt;</span><br></pre></td></tr></table></figure></div></li><li><p>These are actually calling to variable <code>x</code> and <code>y</code>. As you can see, they are also placeholders like the <code>foo</code> one mentioned above.</p></li><li><p>Inside <code>main.o</code> lies a table(Relocatable Section) indicating the symbols relocated from other elf files.</p></li><li><p><code>readelf -a main.o</code> :</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">Relocation section &#x27;.rela.text.startup.main&#x27; at offset 0x198 contains 3 entries:</span><br><span class="line">Offset          Info           Type           Sym. Value      Sym. Name + Addend</span><br><span class="line">000000000006  000400000002 R_X86_64_PC32     0000000000000000 y - 4</span><br><span class="line">00000000000c  000500000002 R_X86_64_PC32     0000000000000000 x - 4</span><br><span class="line">000000000011  000600000004 R_X86_64_PLT32    0000000000000000 foo - 4</span><br></pre></td></tr></table></figure></div></li><li><p>The reason why the address of <code>foo</code> needs to be substracted by 4 is that, the offset encoded in <code>call</code> instruction is calculated relative to the address of the next instruction, which is 4 bytes ahead of the current instruction on x86-64.</p></li></ul><details class="relative my-4 border border-border-color bg-second-background-color rounded-md  green" data-header-exclude><summary class="px-4 py-2 rounded-md shadow-[0_0_2px_0_var(--shadow-color-1)] cursor-pointer not-markdown"><i class="fa-solid fa-chevron-right"></i>Fun_Fact</summary><div class="content p-4 "><ul><li>This <code>-4</code> adjustment is a platform-related design – pc always points to the next instruction in x86, which is likely intended to make shift for the poor hardware in the age of 1970s.</li><li>On modern platforms like Risc-V <em>(some ARM modes as execption)</em>, pc will always point to the current executing instruction, so there’s no need of <code>-4</code> adjustment.</li></ul></div></details></li></ul><hr><div class="callout callout--titled info mb-4 rounded-small shadow-redefine-flat bg-(--callout-bg-color) p-3 pl-1 relative flex flex-row gap-2"><div role="none" class="rounded-full self-stretch w-0.5 bg-(--callout-primary-color) shrink-0 opacity-60"></div><div class="flex flex-col gap-2"><div class="callout__title flex items-center gap-2 font-semibold tracking-tight"><i class="callout__icon fa-circle-question leading-none text-(--callout-primary-color) text-sm shrink-0"></i> Whimsy</div><div class="callout__content markdown-body flex-1 min-w-0"><ol><li>Is it possible to ‘hack’ someone’s machine by tamperring this relocation process? Hijack the program from executing the original function and redirect it to execute another injected function? <em>(Turns out this way of ‘hacking’ has real-world example, which is GOT overwrite.)</em></li><li>Is it the way of counting the usage(benchmark) of a certain function? Relocating it to a wrapper function which marks the usage and the time consumed and then jump to the intended function? <em>(It seems that there are better options.)</em></li></ol></div></div></div><hr><ul><li><p>Also, we can use <code>nm</code> to take a look at the symbol table stored inside <code>main.o</code>:</p><div class="code-container" data-rel="Bash"><figure class="iseeu highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"> nm main.o</span><br><span class="line">                 U foo</span><br><span class="line">0000000000000000 T main</span><br><span class="line">                 U x</span><br><span class="line">                 U y</span><br></pre></td></tr></table></figure></div></li></ul><div class="callout callout--titled info mb-4 rounded-small shadow-redefine-flat bg-(--callout-bg-color) p-3 pl-1 relative flex flex-row gap-2"><div role="none" class="rounded-full self-stretch w-0.5 bg-(--callout-primary-color) shrink-0 opacity-60"></div><div class="flex flex-col gap-2"><div class="callout__title flex items-center gap-2 font-semibold tracking-tight"><i class="callout__icon fa-circle-exclamation leading-none text-(--callout-primary-color) text-sm shrink-0"></i> Note:</div><div class="callout__content markdown-body flex-1 min-w-0"><p><code>a.out</code> compiled&#x2F;linked with <code>-static</code> flag would contain symbols from <code>glibc</code> as static linkage would link <code>glibc</code> by default.<br>To get an <code>a.out</code> executable with a clean symbol table, one should remove the <code>-static</code> flag from the compilation command.</p></div></div></div><h3 id="What-Else"><a href="#What-Else" class="headerlink" title="What Else?"></a>What Else?</h3><ul><li>Actually, <code>gcc</code> does not only link those code&#x2F;data we’ve written in that three source files. It also links a lot of other standard libraries to ensure the proper execution of the program.</li><li>Take a look at the verbose information output by <code>gcc a.o b.o main.o -Wl,--verbose</code> and you’ll see that gcc uses <code>ld</code> to link a lot of files including <code>a.o</code>, <code>b.o</code> and <code>main.o</code>.</li><li>As a result, linking them only using ld won’t be enough: <code>ld a.o b.o main.o</code> would output an <code>a.out</code> that would trigger a <em>Segmentation Fault</em>.</li></ul><h2 id="Dynamic-Linking"><a href="#Dynamic-Linking" class="headerlink" title="Dynamic Linking"></a>Dynamic Linking</h2><ul><li><p>Static linking would create a large executable file as it includes the entire code of the used libraries. When it comes to larger project which links a certain amount of libraries, the size of the executable may become significantly large, making it impractical for deployment or storage.</p></li><li><p>Dynamic linking was invented to resolve this issue: The system loads the requried shared libraries into memory(if not already loaded) when the executable is run and resolves the symbols.</p></li><li><p>Running executables compiled and linked with dynamic linking strategy is actually passing it to a dynamic linker.</p></li><li><p>We can use <code>readelf -l a.out | grep interpreter</code> to get the dynamic linker used for executing:</p><div class="code-container" data-rel="Bash"><figure class="iseeu highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"> readelf -l a.out | grep interpreter</span><br><span class="line">  [Requesting program interpreter: /lib64/ld-linux-x86-64.so.2]</span><br></pre></td></tr></table></figure></div></li></ul><hr><ul><li>There’s a lot more about dynamic linking not mentioned in this class, as it’s too complicated and has too much details.</li></ul>]]>
    </content>
    <id>https://blog.imlast.top/2024/12/20/nju-pa-w9/</id>
    <link href="https://blog.imlast.top/2024/12/20/nju-pa-w9/"/>
    <published>2024-12-20T03:00:32.000Z</published>
    <summary>Notes taken for class W9 of NJU PA</summary>
    <title>
      <![CDATA[PA W9 - Linking & Loading]]>
    </title>
    <updated>2024-12-20T03:00:32.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>Last</name>
    </author>
    <category term="工具使用" scheme="https://blog.imlast.top/categories/%E5%B7%A5%E5%85%B7%E4%BD%BF%E7%94%A8/"/>
    <category term="wayland" scheme="https://blog.imlast.top/tags/wayland/"/>
    <category term="hyprland" scheme="https://blog.imlast.top/tags/hyprland/"/>
    <content>
      <![CDATA[<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><h3 id="初尝-Hyprland"><a href="#初尝-Hyprland" class="headerlink" title="初尝 Hyprland"></a>初尝 Hyprland</h3><ul><li>大约半年前，我被 hyprland 各种绚丽的动效吸引，不顾 N 卡的艰难险阻在笔记本上安装了 hyprland。事实证明，这不是一次良好的体验。</li><li>回看当初的记录，其实大多数的麻烦都是 N 卡和输入法造成的。诸如 nvidia drm 和驱动钩子的设置，以及众多基于 Electron(Chromium) 的应用中，n 卡驱动造成的闪烁和输入法问题着实是费了我半天工夫。</li><li>而如今，我已改用 A 卡，顿感浑身清爽。</li></ul><h3 id="开箱即用-从零配置"><a href="#开箱即用-从零配置" class="headerlink" title="开箱即用 || 从零配置"></a>开箱即用 || 从零配置</h3><ul><li>在初入 DE Rice 领域的时候，我曾坚信只有由自己亲手去写（缝） dotfiles，搞明白每一个配置选项和桌面环境的启动流程，才能保证在某个桌面组件出问题的时候可以快速定位到问题源头并予以修复。直接套用他人提供的配置文件看似在配置阶段省下了不少时间，实则会在将来的某一刻加倍偿还。</li><li>虽然如今我依然这样觉得，但是已然不愿意付出折腾系统美化所消耗的时间和精力代价了。</li></ul><hr><ul><li>所以这次我选择了一种折中的方案：套用 mylinuxforwork 的 <a class="link"   href="https://github.com/mylinuxforwork/dotfiles" >dotfile<i class="fa-solid fa-arrow-up-right ml-[0.2em] font-light align-text-top text-[0.7em] link-icon"></i></a>（该配置套件甚至在 Hyprland 的官方 <a class="link"   href="https://wiki.hyprland.org/" >wiki<i class="fa-solid fa-arrow-up-right ml-[0.2em] font-light align-text-top text-[0.7em] link-icon"></i></a> 中得到推荐），在此基础之上加入&#x2F;修改一些自己定制的配置方案。</li><li>当然，这一切也是基于我过去很长一段时间内使用 Bspwm 和上一次配置 Hyprland 的经验才能实现。毕竟，深度定制桌面环境配置的前提依然是能看懂各个配置项的作用。</li></ul><h3 id="Nvidia"><a href="#Nvidia" class="headerlink" title="Nvidia"></a>Nvidia</h3><ul><li><p>Nvidia 是令许多 Wayland 用户感到头痛的硬件，其驱动（厂商）与 Wayland 社区之间长久的矛盾导致 Wayland 对于 Nvidia（又或者说是 Nvidia 对于 Wayland）的支持在很长时间内都处于一个十分尴尬的情况。</p></li><li><p>根据这个 <a class="link"   href="https://www.reddit.com/r/linux/comments/1b61kiz/eli5_why_doesnt_nvidia_play_well_with_wayland/" >Reddit Thread<i class="fa-solid fa-arrow-up-right ml-[0.2em] font-light align-text-top text-[0.7em] link-icon"></i></a>，Wayland 与 Nvidia 之间的主要矛盾在于所使用的 GPU 缓存管理 API：Wayland 希望使用 GBM，而 Nvidia 则拒绝了 Wayland 的提案，要求使用自家开发的 EGLStream。</p></li><li><p>事实上，绝大部分的 Linux 桌面和显卡驱动生态中所使用的都是 GBM，而 EGLStream 则只有 Nvidia 在他们的驱动中使用。EGLStream 高度封装且闭源，倘若要将 EGLStream 推广到各个系统上，可能会导致其他的硬件厂商（AMD，Intel）为了兼容自己的硬件设备而开发各种各样的 API 扩展，这对于 Wayland 的社区开发者而言是一个绝对不想看到的局面。</p></li><li><p>此外，在 Wayland 的早期开发&#x2F;讨论会议中，Nvidia 受邀但没有参加。这也是为什么这场关于 GBM vs EGLStream 的纷争持续了如此之久的原因之一。</p></li><li><p>在多年的协商后，Nvidia 最终还是为其驱动程序添加了 GBM API 接口。尽管这些接口的使用仍然不尽如人意，也总要好过 GBLStream 时期。</p></li></ul><hr><ul><li>如日中天的 Nvidia 对于开源世界的傲慢姿态再一次体现了资本的最终目标是增值，除此以外的一切事物他们并不关心。</li></ul><h2 id="安装"><a href="#安装" class="headerlink" title="安装"></a>安装</h2><ul><li>Mylinuxforwork 的 dotfiles 提供了安装脚本，非常方便。安装脚本还自带了许多安装选项，甚至自带备份旧配置文件的功能。</li><li>具体的备份位置在 <code>~/.ml4w-hyprland/backup</code>。</li></ul><hr><ul><li>注意到 ml4w 自动安装了一些我不需要的包，例如 <code>breeze</code> 和 <code>firefox</code> （我使用 Brave），遂在安装完成后将它们删除。</li><li>我本来以为删除 breeze 可能会导致某些 icon 出现问题，后来发现其实没什么影响。</li></ul><h2 id="配置介绍"><a href="#配置介绍" class="headerlink" title="配置介绍"></a>配置介绍</h2><h3 id="Waybar-Workspace"><a href="#Waybar-Workspace" class="headerlink" title="Waybar &amp;&amp; Workspace"></a>Waybar &amp;&amp; Workspace</h3><ul><li>Wayland 下的 bar 一般使用 <a class="link"   href="https://github.com/Alexays/Waybar" >waybar<i class="fa-solid fa-arrow-up-right ml-[0.2em] font-light align-text-top text-[0.7em] link-icon"></i></a>，ml4w 的自带的设置中可以调整 waybar 中的许多配置项，例如时间显示格式，workspace 数量以及是否展示某些应用图标等等。</li><li>一开始我以为要想修改默认的 workspace 数量需要在 waybar 的配置文件中改，后来发现并非如此。</li><li>ml4w 配置中虽然确实仅有 waybar 的配置文件（<code>~/.config/waybar/modules.json</code>）中可以找到 workspace 相关的配置，但想要设置 hyprland workspace 相关的配置项需要在 <code>~/.config/hypr/</code> 目录下新建 workspace 相关的配置文件。</li></ul><h3 id="Settings-App-Waypaper"><a href="#Settings-App-Waypaper" class="headerlink" title="Settings App &amp;&amp; Waypaper"></a>Settings App &amp;&amp; Waypaper</h3><ul><li><p>ml4w 中自带一个设置应用，可以用于设置许多配置中的选项。在 ml4w 中，作者并不建议用户直接修改默认的配置文件。正确的方式是新建一份自己的配置文件，然后在 Settings App 中选择自定义的配置文件。</p></li><li><p>ml4w 还有一个可以选择壁纸的应用（waypaper），选择完成后还会自动修改 waybar 和 kitty 等应用的主体色。</p></li><li><p>说实话，默认的这些个壁纸都不是很符合我的口味，然而也不愿再花大精力去挑选壁纸了，日后再议吧。</p></li></ul><h2 id="配置修改"><a href="#配置修改" class="headerlink" title="配置修改"></a>配置修改</h2><h3 id="新窗口设置为右侧打开"><a href="#新窗口设置为右侧打开" class="headerlink" title="新窗口设置为右侧打开"></a>新窗口设置为右侧打开</h3><ul><li><p>其实刚刚开始使用这份配置的时候我就发现了这个在上次使用 hyprland 的时候也遇到过的问题。上次的时候因为 n 卡和输入法的各种 bug 导致我还没来得及排查这个问题就直接放弃跑路了，这次终于得以窥得这其中的缘由和解决方案。</p></li><li><p>我开始的时候在 wiki 里找了半天，搜索了诸如 <code>workspace</code>, <code>window-rules</code>, <code>position</code> 等等，甚至还研究了一下 <code>special workspace</code>。最后在一个 reddit 论坛上发现，该配置项另有他人：</p></li><li><p>想要使新窗口始终在右侧打开，需设置 layout 规则如下：</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">dwindle &#123;</span><br><span class="line">    ...</span><br><span class="line">    force_split = 2</span><br><span class="line">    ...</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div></li><li><p>说实话，不是很明白什么人会允许窗口的打开位置随着随手一挥而不知道在什么地方等待指令的鼠标一样不知所踪。</p></li></ul><h3 id="多显示器-Workspace-设置"><a href="#多显示器-Workspace-设置" class="headerlink" title="多显示器 Workspace 设置"></a>多显示器 Workspace 设置</h3><ul><li><p>尝试过修改 waybar 中的 <code>persistent-workspaces</code>，不起作用。</p></li><li><p>后在 hyprland 配置中增加如下内容：</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">workspace = 1, monitor:DP-1, persistent:true, default:true</span><br><span class="line">workspace = 2, monitor:DP-1, persistent:true, default:true</span><br><span class="line">workspace = 3, monitor:DP-1, persistent:true, default:true</span><br><span class="line">workspace = 4, monitor:DP-1, persistent:true, default:true</span><br><span class="line">workspace = 5, monitor:DP-1, persistent:true, default:true</span><br><span class="line">workspace = 6, monitor:DP-2, persistent:true, default:true</span><br><span class="line">workspace = 7, monitor:DP-2, persistent:true, default:true</span><br><span class="line">workspace = 8, monitor:DP-2, persistent:true, default:true</span><br><span class="line">workspace = 9, monitor:DP-2, persistent:true, default:true</span><br><span class="line">workspace = 10, monitor:DP-2, persistent:true, default:true</span><br></pre></td></tr></table></figure></div></li><li><p>另，加入如下的 keybindings 配置：</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">bind = $mainMod, bracketleft, workspace, m-1</span><br><span class="line">bind = $mainMod, bracketright, workspace, m+1</span><br><span class="line"></span><br><span class="line">bind = $mainMod CTRL, H, movetoworkspace, m-1</span><br><span class="line">bind = $mainMod CTRL, L, movetoworkspace, m+1</span><br></pre></td></tr></table></figure></div></li><li><p>其中，<code>m-1</code> 中的 <code>m</code> 代表 <code>monitor</code>，这样设置是为了防止在左边屏幕上切换 workspace 时不慎切到右边的屏幕上。</p></li><li><p>然而 hyprland 中的 workspace 似乎并不是在启动时初始化的，而是在被切换到的时候才会被初始化。这导致每次启动 hyprland 的时候，快捷键 <code>super + [/]</code> 切换不到任何 workspace 上（因为那时候只有 workspace 1 和 6 被初始化），之后在用鼠标点击 waybar 中的各个 workspace 一次，将它们手动初始化后才能正常切换。</p></li><li><p>一个不怎么优雅的解决方案是在 hyprland 中加入如下的配置：</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">exec = for i in &#123;1..10&#125;; do hyprctl dispatch workspace $i; done &amp;&amp; hyprctl dispatch workspace 1</span><br></pre></td></tr></table></figure></div></li><li><p>之所以不那么优雅，是因为 workspace 1 是在左侧屏幕上的，该命令执行完毕而右侧的屏幕也会停留在 workspace 1 上，然而显示的窗口其实是 workspace 10 中的。</p></li></ul><hr><ul><li>虽然不能复现 bspwm 中那样两块屏幕分离并可以独自循环的 Workspace，不过也算是可堪一用了。</li></ul><h3 id="窗口透明度调整"><a href="#窗口透明度调整" class="headerlink" title="窗口透明度调整"></a>窗口透明度调整</h3><ul><li><p>默认配置中，inactive 的窗口透明度会被调整为 0.8。对于某些窗口来说，这并非良好的体验。</p></li><li><p>调整如下：</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">windowrulev2 = opacity 1.0 override 1.0 override 1.0, class:(brave-browser)</span><br><span class="line">windowrulev2 = opacity 1.0 override 1.0 override 1.0, class:(QQ)</span><br><span class="line">windowrulev2 = opacity 1.0 override 1.0 override 1.0, class:(discord)</span><br></pre></td></tr></table></figure></div></li></ul><h2 id="BUG-及修复方式"><a href="#BUG-及修复方式" class="headerlink" title="BUG 及修复方式"></a>BUG 及修复方式</h2><h3 id="Waybar-Taskbar-中的某些应用图标未知"><a href="#Waybar-Taskbar-中的某些应用图标未知" class="headerlink" title="Waybar Taskbar 中的某些应用图标未知"></a>Waybar Taskbar 中的某些应用图标未知</h3><ul><li><p>有时候，窗口的 <code>class</code> 可能会和其使用的 icon 文件名有所不同，例如 Cherry Studio：</p><ul><li><p><code>hyprctl clients</code>:</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">...</span><br><span class="line">workspace: 7 (7)</span><br><span class="line">floating: 0</span><br><span class="line">pseudo: 0</span><br><span class="line">monitor: 1</span><br><span class="line">class: CherryStudio</span><br><span class="line">title: Cherry Studio</span><br><span class="line">initialClass: CherryStudio</span><br><span class="line">initialTitle: CherryStudio</span><br><span class="line">...</span><br></pre></td></tr></table></figure></div></li><li><p><code>fd cherry /usr/share/icons</code>:</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">/usr/share/icons/hicolor/256x256/apps/cherry-studio.png</span><br></pre></td></tr></table></figure></div></li></ul></li><li><p>如果是这种情况，我们需要为 Waybar 的 Taskbar 添加 <code>app_ids-mapping</code> 配置：</p><ul><li>在 <code>~/.config/waybar/modules.json</code> 中：</li></ul><div class="code-container" data-rel="Json"><figure class="iseeu highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">    <span class="comment">// ...</span></span><br><span class="line">    <span class="attr">&quot;wlr/taskbar&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">        <span class="comment">// ...,</span></span><br><span class="line">        <span class="attr">&quot;app_ids-mapping&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">            <span class="attr">&quot;CherryStudio&quot;</span><span class="punctuation">:</span> <span class="string">&quot;cherry-studio&quot;</span></span><br><span class="line">        <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="comment">// ...</span></span><br><span class="line">    <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure></div></li><li><p>重启 waybar 后即可观察到图标正常显示。</p></li></ul><h3 id="Electron-IME"><a href="#Electron-IME" class="headerlink" title="Electron IME"></a>Electron IME</h3><ul><li><p>Electron 应用需要添加特定的 flag 来开启 Wayland 及输入法支持：</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">--ozone-platform-hint=auto</span><br><span class="line">--enable-wayland-ime</span><br></pre></td></tr></table></figure></div></li><li><p>此外，可以使用 <code>flag.conf</code> 的方式，以 <code>linuxqq-nt-bwrap</code> 为例，需要在 <code>~/.config/qq-electron-flags.conf</code> 中写入：</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">--ozone-platform-hint=auto</span><br><span class="line">--enable-wayland-ime</span><br></pre></td></tr></table></figure></div></li></ul><div class="callout callout--titled info mb-4 rounded-small shadow-redefine-flat bg-(--callout-bg-color) p-3 pl-1 relative flex flex-row gap-2"><div role="none" class="rounded-full self-stretch w-0.5 bg-(--callout-primary-color) shrink-0 opacity-60"></div><div class="flex flex-col gap-2"><div class="callout__title flex items-center gap-2 font-semibold tracking-tight"><i class="callout__icon fa-clock leading-none text-(--callout-primary-color) text-sm shrink-0"></i> 更新</div><div class="callout__content markdown-body flex-1 min-w-0"><p>在某个版本（2025-9-08）之后，Electron 应用在 Wayland 环境下默认添加 <code>--ozone-platform-hint=wayland</code>，但 Fcitx5 仍需要 <code>enable-wayland-ime</code> 以启用。</p></div></div></div><h3 id="硬件加速（重要）"><a href="#硬件加速（重要）" class="headerlink" title="硬件加速（重要）"></a>硬件加速（重要）</h3><ul><li>详见 <a class="link"   href="https://wiki.archlinux.org/title/Hardware_video_acceleration" >ArchWiki<i class="fa-solid fa-arrow-up-right ml-[0.2em] font-light align-text-top text-[0.7em] link-icon"></i></a></li></ul><h3 id="Brave"><a href="#Brave" class="headerlink" title="Brave"></a>Brave</h3><ul><li><p>与上述 Electron 应用相同，Chromium 也需要添加 <code>--ozone-platform-hint=auto</code> flag 来适配 wayland。若不添加，Brave 的弹出菜单会出现一个“透明”的边框。</p></li><li><p>此外，需要添加 <code>--gtk-version=4</code> 并设置 <code>GTK_IM_MODULE=fcitx</code> 以使用 fcitx5（XWayland）。</p></li><li><p>或在<a class="link"   href="brave://flags" >配置菜单<i class="fa-solid fa-arrow-up-right ml-[0.2em] font-light align-text-top text-[0.7em] link-icon"></i></a>中开启 <code>wayland-ime</code> ，但会导致奇怪的 bug ：</p><ul><li>地址栏内输入的内容会重复出现两次</li><li>输入的内容背景色为亮黄色有些许不美观</li><li>除第一个打开的窗口以外的 brave 窗口均无法使用输入法。</li></ul></li></ul><div class="callout callout--titled info mb-4 rounded-small shadow-redefine-flat bg-(--callout-bg-color) p-3 pl-1 relative flex flex-row gap-2"><div role="none" class="rounded-full self-stretch w-0.5 bg-(--callout-primary-color) shrink-0 opacity-60"></div><div class="flex flex-col gap-2"><div class="callout__title flex items-center gap-2 font-semibold tracking-tight"><i class="callout__icon fa-clock leading-none text-(--callout-primary-color) text-sm shrink-0"></i> 更新</div><div class="callout__content markdown-body flex-1 min-w-0"><p>截止今日（2025-08-20）上述 bug 均以修复。</p></div></div></div><h3 id="OBS"><a href="#OBS" class="headerlink" title="OBS"></a>OBS</h3><ul><li><p>切换至 Wayland 后 OBS 原本使用的屏幕源都无法使用了。经过一翻搜索和折腾，以及绕了一大圈原路，我终于是成功让 OBS 能够添加屏幕源了。</p></li><li><p>安装了 <code>pipewire</code>, <code>wireplumber</code>, <code>xdg-desktop-portal-hyprland</code>，在互联网上检索了半天，测试命令不下十几条，最终都没能让 <code>xdg-desktop-portal-hyprland</code> 服务跑起来。</p></li><li><p>倒是有个邪道方法：直接 <code>dbus-run-session Hyprland</code>，然后再 <code>Ctrl-C</code> 终止，就可以正常启动 <code>xdg-desktop-portal-hyprland</code>。你要问我为什么，那我只能说我也不知道。。</p></li></ul><hr><ul><li>最后发现，其实根本不需要 <code>xdg-desktop-portal-hyprland</code> 作为 <code>xdg-desktop-portal</code> 的后端服务，只要后者正常运行 OBS 就可以捕捉屏幕画面，只要重新添加一个 PipeWire 的 Screen 源就好了。</li><li>此外，重启 OBS 之后该 screen 源会要求重新选择屏幕，并且处于黑屏状态。当我尝试使用 Del 键将其删除时，它又奇迹般的活了过来，令人迷惑不已。</li></ul><div class="callout callout--titled info mb-4 rounded-small shadow-redefine-flat bg-(--callout-bg-color) p-3 pl-1 relative flex flex-row gap-2"><div role="none" class="rounded-full self-stretch w-0.5 bg-(--callout-primary-color) shrink-0 opacity-60"></div><div class="flex flex-col gap-2"><div class="callout__title flex items-center gap-2 font-semibold tracking-tight"><i class="callout__icon fa-clock leading-none text-(--callout-primary-color) text-sm shrink-0"></i> 更新</div><div class="callout__content markdown-body flex-1 min-w-0"><p>后重装系统后发现 obs 又无法捕捉画面了，查阅<a class="link"   href="https://bbs.archlinux.org/viewtopic.pwp?id=293108" >互联网<i class="fa-solid fa-arrow-up-right ml-[0.2em] font-light align-text-top text-[0.7em] link-icon"></i></a>后发现视需要 <code>pipewire-pulse</code>，原因未知。</p></div></div></div><h3 id="VLC"><a href="#VLC" class="headerlink" title="VLC"></a>VLC</h3><ul><li>切换至 Hyprland 后使用 VLC 播放视频只有音频没有视频。</li><li>在 Tools -&gt; Preferences -&gt; Video 中，将 Output 调为 <code>Automatic</code> 后恢复正常。</li><li>我是什么时候又是为什么要把他调成 VDPAU 的来着？</li></ul><h3 id="Timeshift"><a href="#Timeshift" class="headerlink" title="Timeshift"></a>Timeshift</h3><ul><li><p>Timeshift 在 Wayland 上无法启动。</p></li><li><p>解决方案：</p><ol><li><p>将 <code>/usr/share/applications/timeshift-gtk.desktop</code> 复制到 <code>~/.local/share/applications/timeshift-gtk.desktop</code>，修改其中的 <code>Exec</code>:</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Exec = bash -c &#x27;pkexec env $(env) timeshift-launcher&#x27;</span><br></pre></td></tr></table></figure></div></li><li><p>使用 <code>sudo -E timeshift-gtk</code> 来启动 Timeshift。</p></li></ol></li></ul><h3 id="Micromamba"><a href="#Micromamba" class="headerlink" title="Micromamba"></a>Micromamba</h3><ul><li><p>为了使 waypaper 能正常工作，在 <code>.config/hypr/conf/ml4w.conf</code> 中有这样一条配置：</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">env = PYTHONPATH,/usr/lib/python3.12/site-packages:/usr/lib/python3.13/site-packages:$PYTHONPATH</span><br></pre></td></tr></table></figure></div></li><li><p>该配置会导致 micromamba 激活虚拟环境时，<code>PYTHONPATH</code> 变量中多出两个系统级别的 site-packages 路径，导致出现一种“环境混合”的情况。</p></li><li><p>在这种情况下，以系统级别安装的第三方库将出现导入异常的情况，例如 numpy。</p></li><li><p>解决方案：</p><ul><li>在使用 micromamba 激活虚拟环境前，使用 <code>unset PYTHONPATH</code> 指令来清空 <code>PYTHONPATH</code> 中存储的值。</li></ul></li></ul><h2 id="Tips"><a href="#Tips" class="headerlink" title="Tips"></a>Tips</h2><h3 id="查看显示器属性"><a href="#查看显示器属性" class="headerlink" title="查看显示器属性"></a>查看显示器属性</h3><ul><li>使用 <code>hyprctl monitors all</code> 来查看连接到系统的显示器的属性。</li></ul><h3 id="查看窗口属性"><a href="#查看窗口属性" class="headerlink" title="查看窗口属性"></a>查看窗口属性</h3><ul><li>在设置 Window Rule 的时候，我们需要指定规则生效的窗口名称(title），或是类别（class）。</li><li>我们可以使用 <code>hyprctl clients</code> 来查看当前打开的窗口的属性。</li></ul><h3 id="退出-Hyprland"><a href="#退出-Hyprland" class="headerlink" title="退出 Hyprland"></a>退出 Hyprland</h3><ul><li>某些 Hyprland 的配置项并不会立即生效，需要重启 Hyprland。</li><li>使用 <code>hyprctl dispatch exit</code> 来退出到 sddm 界面，输入登录密码重启 Hyprland。</li></ul><h2 id="结束"><a href="#结束" class="headerlink" title="结束"></a>结束</h2><ul><li>就算过了半年，Hyprland 依然是一款非常需要折腾的 wm，即使我已经使用了所谓开箱即用的配件库。</li><li>其中的麻烦有 Wayland 导致的，也有 Hyprland 导致的。Wayland 确实比 X11 年轻不少，但是自从项目自 2008 年正式启动以来也已过了 16 载有余了，项目的推进速度和 bug 修复速度真是慢的让人不知该说什么好。</li><li>话虽如此，还是不得不感叹，相较于 N 卡，A 卡折腾 hyprland 真是相当轻松。</li></ul><hr><ul><li>最后还是要说些好的，wayland&#x2F;hyprland 的动画效果真是舒服。X11 上依靠 picom 的动效相比之下就显得廉价不少了。</li></ul><h2 id="参考资料-网站"><a href="#参考资料-网站" class="headerlink" title="参考资料&#x2F;网站"></a>参考资料&#x2F;网站</h2><h3 id="General"><a href="#General" class="headerlink" title="General"></a>General</h3><ul><li><a class="link"   href="https://github.com/mylinuxforwork/dotfiles" >mylinuxforwork&#x2F;dotfiles - Github<i class="fa-solid fa-arrow-up-right ml-[0.2em] font-light align-text-top text-[0.7em] link-icon"></i></a></li><li><a class="link"   href="https://wiki.hyprland.org/" >Hyprland Wiki<i class="fa-solid fa-arrow-up-right ml-[0.2em] font-light align-text-top text-[0.7em] link-icon"></i></a></li></ul><h3 id="Electron-Chromium-Wayland-Support"><a href="#Electron-Chromium-Wayland-Support" class="headerlink" title="Electron(Chromium) Wayland Support"></a>Electron(Chromium) Wayland Support</h3><ul><li><a class="link"   href="https://wiki.archlinux.org/title/Wayland" >Wayland - Arch Wiki<i class="fa-solid fa-arrow-up-right ml-[0.2em] font-light align-text-top text-[0.7em] link-icon"></i></a></li><li><a class="link"   href="https://wiki.archlinux.org/title/Wayland#Electron" >Wayland: Electron - Arch Wiki<i class="fa-solid fa-arrow-up-right ml-[0.2em] font-light align-text-top text-[0.7em] link-icon"></i></a></li><li><a class="link"   href="https://wiki.archlinux.org/title/Chromium#Native_Wayland_support" >Chromium - Arch Wiki<i class="fa-solid fa-arrow-up-right ml-[0.2em] font-light align-text-top text-[0.7em] link-icon"></i></a></li></ul><h3 id="Fcitx5"><a href="#Fcitx5" class="headerlink" title="Fcitx5"></a>Fcitx5</h3><ul><li><a class="link"   href="https://fcitx-im.org/wiki/Using_Fcitx_5_on_Wayland" >Using Fcitx 5 on Wayland<i class="fa-solid fa-arrow-up-right ml-[0.2em] font-light align-text-top text-[0.7em] link-icon"></i></a></li><li><a class="link"   href="https://www.reddit.com/r/brave_browser/comments/o4x1rr/brave_browser_on_wayland_instead_of_xwayland/" >Brave Browser on Wayland instead of XWayland<i class="fa-solid fa-arrow-up-right ml-[0.2em] font-light align-text-top text-[0.7em] link-icon"></i></a></li></ul><h3 id="Workspace"><a href="#Workspace" class="headerlink" title="Workspace"></a>Workspace</h3><ul><li><a class="link"   href="https://www.reddit.com/r/hyprland/comments/12op694/confused_how_workspaces_function_on_multiple/" >Confused how workspaces function on multiple screens. - Reddit<i class="fa-solid fa-arrow-up-right ml-[0.2em] font-light align-text-top text-[0.7em] link-icon"></i></a></li><li><a class="link"   href="https://www.reddit.com/r/hyprland/comments/1du9fkw/comment/lbfwfaq/" >Issue with waybar, multiple monitors and default&#x2F;persistent workspaces - Reddit<i class="fa-solid fa-arrow-up-right ml-[0.2em] font-light align-text-top text-[0.7em] link-icon"></i></a></li></ul><h3 id="Screen-Sharing-OBS"><a href="#Screen-Sharing-OBS" class="headerlink" title="Screen Sharing(OBS)"></a>Screen Sharing(OBS)</h3><ul><li><a class="link"   href="https://wiki.hyprland.org/Useful-Utilities/Screen-Sharing/" >Screen sharing - Hypr Wiki<i class="fa-solid fa-arrow-up-right ml-[0.2em] font-light align-text-top text-[0.7em] link-icon"></i></a></li><li><a class="link"   href="https://wiki.hyprland.org/hyprland-wiki/pages/Useful-Utilities/Hyprland-desktop-portal/" >Hyprland-dexktop-portal<i class="fa-solid fa-arrow-up-right ml-[0.2em] font-light align-text-top text-[0.7em] link-icon"></i></a></li><li><a class="link"   href="https://gist.github.com/brunoanc/2dea6ddf6974ba4e5d26c3139ffb7580#troubleshooting-obs" >Screen sharing on Hyprland(Arch Linux) - Github Gist<i class="fa-solid fa-arrow-up-right ml-[0.2em] font-light align-text-top text-[0.7em] link-icon"></i></a></li><li><a class="link"   href="https://wiki.archlinux.org/title/Screen_capture#Wayland" >Screen Capture - Arch Wiki<i class="fa-solid fa-arrow-up-right ml-[0.2em] font-light align-text-top text-[0.7em] link-icon"></i></a></li><li><a class="link"   href="https://www.reddit.com/r/hyprland/comments/182q2tf/how_to_capture_screen_use_obs_just_black_screen/" >How to capture screen use obs just black screen now? - Reddit<i class="fa-solid fa-arrow-up-right ml-[0.2em] font-light align-text-top text-[0.7em] link-icon"></i></a></li></ul><h3 id="VLC-1"><a href="#VLC-1" class="headerlink" title="VLC"></a>VLC</h3><ul><li><a class="link"   href="https://askubuntu.com/questions/668834/vlc-media-player-is-not-displaying-video-but-audio-works" >VLC media player is not displaying video, but audio works - AskUbuntu<i class="fa-solid fa-arrow-up-right ml-[0.2em] font-light align-text-top text-[0.7em] link-icon"></i></a></li></ul><h3 id="Others"><a href="#Others" class="headerlink" title="Others"></a>Others</h3><p><a class="link"   href="https://www.reddit.com/r/linux/comments/1b61kiz/eli5_why_doesnt_nvidia_play_well_with_wayland/" >ELI5: Why doesn’t Nvidia play well with wayland? - Reddit<i class="fa-solid fa-arrow-up-right ml-[0.2em] font-light align-text-top text-[0.7em] link-icon"></i></a></p>]]>
    </content>
    <id>https://blog.imlast.top/2024/12/18/2nd-hyprland/</id>
    <link href="https://blog.imlast.top/2024/12/18/2nd-hyprland/"/>
    <published>2024-12-18T18:21:09.000Z</published>
    <summary>记录再次尝试 hyprland 的过程，以及初步的配置结果</summary>
    <title>Hyprland 二周目</title>
    <updated>2024-12-18T18:21:09.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>Last</name>
    </author>
    <category term="笔记" scheme="https://blog.imlast.top/categories/%E7%AC%94%E8%AE%B0/"/>
    <category term="c" scheme="https://blog.imlast.top/tags/c/"/>
    <category term="笔记" scheme="https://blog.imlast.top/tags/%E7%AC%94%E8%AE%B0/"/>
    <content>
      <![CDATA[<div class="callout callout--titled warning mb-4 rounded-small shadow-redefine-flat bg-(--callout-bg-color) p-3 pl-1 relative flex flex-row gap-2"><div role="none" class="rounded-full self-stretch w-0.5 bg-(--callout-primary-color) shrink-0 opacity-60"></div><div class="flex flex-col gap-2"><div class="callout__title flex items-center gap-2 font-semibold tracking-tight"><i class="callout__icon fa-triangle-exclamation leading-none text-(--callout-primary-color) text-sm shrink-0"></i> Warning</div><div class="callout__content markdown-body flex-1 min-w-0"><p>If someone is reading this blog, please be aware that the writer <strong>DID NOT</strong> consider the experience of the other readers.<br>After all, the most important thing is about writing things down for better memorization.</p></div></div></div><h2 id="IO-Interaction-Between-AM-and-NEMU"><a href="#IO-Interaction-Between-AM-and-NEMU" class="headerlink" title="IO Interaction Between AM and NEMU"></a>IO Interaction Between AM and NEMU</h2><ul><li>Generally speaking, NEMU would register some special memory address as MMIO abstract registers, such as <code>CONFIG_AUDIO_CTL_MMIO</code><em>(0xa0000200)</em> when initializing corresponding devices.</li><li>When the guest program(application) is trying to read&#x2F;write data from these special memory address, NEMU would intercept those read&#x2F;write operations and run the correlated callback functions which are set up with the special register, to simulate read&#x2F;write op to hardware registers.</li></ul><h2 id="Timer"><a href="#Timer" class="headerlink" title="Timer"></a>Timer</h2><ul><li>The memory address of RTC is <em>0xa0000048</em>, where NEMU registers 8 bytes of memory space.</li><li>Accessing this memory area would invoke function <code>rtc_io_handler</code> which effectively returns the uptime in us as a 64-bit int.</li><li>As AM could conduct a read operation of at most 32 bits(<code>inl</code>), we have to ‘read’ twice for the complete 64-bit data.</li><li>The first 4 bytes starting from <em>0xa0000048</em> is the lower part of the 8-byte uptime.</li></ul><h2 id="Keyboard"><a href="#Keyboard" class="headerlink" title="Keyboard"></a>Keyboard</h2><ul><li><p>There seems to be quite a lot of things going on in <code>nemu/src/device/keyboard.c</code>, but it’s a quite simple task as long as one understand the interaction logic between AM and NEMU, as there’re only 4 bytes(AKA 1 32-bit int) representing a keycode being registered at <code>CONFIG_I8042_DATA_MMIO</code><em>(0xa0000060)</em>.</p></li><li><p>There’s an interesting design <code>#define KEYDOWN_MASK 0x8000</code> in AM. It is basically an indicator of whether the key is pressed or released. Here’s how it works:</p><ul><li>First of all, a convention in NEMU states that, if the keycode’s 15th bit is non-zero, that means the key is pressed down, and vice versa.</li><li>Then, we can use this mask to judge whether the keycode represents a pressed key or a released one by conducting bitwise operation AND(&amp;) between the keycode and the <code>KEYDOWN_MASK</code>, if the result is <code>1</code>, that means a pressed key, and vice versa.</li></ul></li></ul><hr><div class="callout callout--titled info mb-4 rounded-small shadow-redefine-flat bg-(--callout-bg-color) p-3 pl-1 relative flex flex-row gap-2"><div role="none" class="rounded-full self-stretch w-0.5 bg-(--callout-primary-color) shrink-0 opacity-60"></div><div class="flex flex-col gap-2"><div class="callout__title flex items-center gap-2 font-semibold tracking-tight"><i class="callout__icon fa-circle-info leading-none text-(--callout-primary-color) text-sm shrink-0"></i> Fun_Fact</div><div class="callout__content markdown-body flex-1 min-w-0"><p>This 15th bit convention is primarily a Windows-specific implementation detail.<br>Other systems have other conventions, like using <code>XKeyEvent</code> structure on X11(Linux&#x2F;Unix).</p></div></div></div><h2 id="VGA"><a href="#VGA" class="headerlink" title="VGA"></a>VGA</h2><ul><li><p>This is a quite troublesome part. Here’s the deal:</p><ul><li>The guest program(application) could write frame data to frame buffer register(<code>CONFIG_FB_ADDR</code><em>0xa1000000</em>) with the api provided by AM(<code>io_write</code>).</li><li>AM would then write the pixels sent by the client to <code>FB_ADDR</code>(same as <code>CONFIG_FB_ADDR</code>). There’re no callback function for frame buffer in NEMU, so it is direct write to <code>vmem</code>.</li><li>NEMU constantly updates vga, whenever the value stored in the abstract sync register turns into <code>1</code>, NEMU will use the data in frame buffer to update the screen.</li></ul></li></ul><hr><ul><li><p>The code in <code>am-kernels/kernels/typing-game</code> is really fun, especially this part:</p><div class="code-container" data-rel="C"><figure class="iseeu highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">for</span> (<span class="type">int</span> ch = <span class="number">0</span>; ch &lt; <span class="number">26</span>; ch++) &#123;</span><br><span class="line">    <span class="type">char</span> *c = &amp;font[CHAR_H * ch];</span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">0</span>, y = <span class="number">0</span>; y &lt; CHAR_H; y++)</span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> x = <span class="number">0</span>; x &lt; CHAR_W; x++, i++) &#123;</span><br><span class="line">            <span class="type">int</span> t = (c[y] &gt;&gt; (CHAR_W - x - <span class="number">1</span>)) &amp; <span class="number">1</span>;</span><br><span class="line">            texture[WHITE][ch][i] = t ? COL_WHITE : COL_PURPLE;</span><br><span class="line">            texture[GREEN][ch][i] = t ? COL_GREEN : COL_PURPLE;</span><br><span class="line">            texture[RED][ch][i] = t ? COL_RED : COL_PURPLE;</span><br><span class="line">        &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div></li><li><p>This is where each english letter is ‘pre-painted’. By checking each bit(pixel) of the letter with bitwise operation, the program effectively find out the alpha value of each pixel. Then, it writes color data into the corresponding place in <code>texture</code>, <code>COL_PURPLE</code> for zero-alpha pixel.</p></li></ul><h2 id="Audio"><a href="#Audio" class="headerlink" title="Audio"></a>Audio</h2><ul><li><p>This is the most difficult part of the emulated device done in PA2. Mostly because it requires a ring buffer queue which is written and read by differenct programs(AM &amp; NEMU). Also, Learning SDL is also a troublesome part, cuz I really don’t consider its official wiki very friendly.</p></li><li><p>Most of my time was spent on tuning the ring buffer, which is kinda like a producer-consumer model I learnt in OS lessons before. It was the first time that I implement some theoretical OS stuff into actual program, it really took me a while to fix all these bugs created by a fool filled with ignorance, which is me…</p></li></ul><hr><ul><li><p>Anyway, the core idea is like this:</p><ul><li>The guest program(application) could write audio data to sound buffer register(<code>CONFIG_SB_ADDR</code>).</li><li>AM would then write the audio data to the queue(<code>sbuf</code>, the ring buffer) when there’re enough space.</li><li>NEMU would read audio data from the queue(<code>sbuf</code>) and copy them to SDL2’s stream. BTW, this process is actually done in SDL_Audio’s callback function.</li></ul></li></ul><hr><ul><li><p>Key difficulties when maintaining <code>sbuf</code>:</p><ul><li><p>When AM writes audio data:</p><ol><li>Check whether there’re enough space(<code>count + len &lt;= sbuf_size</code>). If not, halt until there are.</li><li>Check whether the write-pointer has reached the boundary of the queue. If so, reset the pointer to the start, and write the rest of the data.</li></ol></li><li><p>When NEMU reads audio data:</p><ol><li>Check whether there’re valid data in <code>sbuf</code>(<code>count &gt;= 0</code>). If not, return.</li><li>Check whether the read-pointer has reached the boundary of the queue. If so, reset the pointer to the start, and read the rest of the required data.</li></ol></li></ul></li><li><p>In real practice, the speed of AM writing audio data is significantly faster than NEMU reading, as the frequency of calling SDL2’s callback function is pretty low.</p></li></ul><h2 id="End-of-PA2"><a href="#End-of-PA2" class="headerlink" title="End of PA2"></a>End of PA2</h2><ul><li>It’s been quite a journey since I started learning PA. I consider myself utterly fortunate to have found a lesson that aligns so perfectly with what psychologists called the <em>Zone of Proximal Development</em>. PA has been exactly that for me – a space where I can challenge myself while steadily improving.</li><li>When I first began learning PA, I couldn’t even write a basic linked list in C. Now, I found myself maintaining a ring buffer queue that is accessed by differenct programs. How exciting to see how far I’ve come!</li></ul><hr><ul><li>I am not a student at NJU, not even a CS major student to be precise. I am also not a 985&#x2F;211 university student. However, my passion for programming, curiosity about the knowledge behind the computing machine, and the desire for meaningful challenges have guided me to this point. This lesson, PA, has shown me the way of learning. I know there’s still a long way to go, but the courage I’ve gained will surely motivate me to continue the rest of my journey.</li><li>Looking back, now I feel my college life has not been wasted.</li></ul><hr><p>I want to express my gratitude to:</p><ul><li>Dr. <strong>HuiYan Wang</strong> (<a class="link"   href="http://www.why.ink:8080/" >http://www.why.ink:8080<i class="fa-solid fa-arrow-up-right ml-[0.2em] font-light align-text-top text-[0.7em] link-icon"></i></a>) for her excellent <a class="link"   href="https://www.bilibili.com/video/BV11BpFe4EmM" >open classes<i class="fa-solid fa-arrow-up-right ml-[0.2em] font-light align-text-top text-[0.7em] link-icon"></i></a> for PA on Bilibili.</li><li>Dr. <strong>ZiHao Yu</strong> (<a class="link"   href="https://sashimi-yzh.github.io/" >https://sashimi-yzh.github.io<i class="fa-solid fa-arrow-up-right ml-[0.2em] font-light align-text-top text-[0.7em] link-icon"></i></a>) for his brilliant emulator <a class="link"   href="https://github.com/NJU-ProjectN/nemu" >NEMU<i class="fa-solid fa-arrow-up-right ml-[0.2em] font-light align-text-top text-[0.7em] link-icon"></i></a> and the detailed <a class="link"   href="https://nju-projectn.github.io/ics-pa-gitbook/ics2024/index.html" >manual<i class="fa-solid fa-arrow-up-right ml-[0.2em] font-light align-text-top text-[0.7em] link-icon"></i></a> for PA.</li><li>The members of the ysyx QQ group, who provided invaluable support.</li><li>And, of course, the vast resources available on the Internet – wiki, documentation, forums – and the assistance from LLMs like ChatGPT and Claude.</li></ul><hr><p>I will be resting for a while, and then head for the pre-learning of ysyx, which is basicaly knowledge and expirement of Digital Circuit.</p><p>Cheers!</p>]]>
    </content>
    <id>https://blog.imlast.top/2024/11/22/nju-pa-2-part2/</id>
    <link href="https://blog.imlast.top/2024/11/22/nju-pa-2-part2/"/>
    <published>2024-11-22T10:10:25.000Z</published>
    <summary>Implementing emulated device such as timer, keyboard, VGA and audio</summary>
    <title>PA2 Part 2 - Emulated Hardware Device</title>
    <updated>2024-11-22T10:10:25.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>Last</name>
    </author>
    <category term="笔记" scheme="https://blog.imlast.top/categories/%E7%AC%94%E8%AE%B0/"/>
    <category term="c" scheme="https://blog.imlast.top/tags/c/"/>
    <category term="pa" scheme="https://blog.imlast.top/tags/pa/"/>
    <content>
      <![CDATA[<div class="callout callout--titled warning mb-4 rounded-small shadow-redefine-flat bg-(--callout-bg-color) p-3 pl-1 relative flex flex-row gap-2"><div role="none" class="rounded-full self-stretch w-0.5 bg-(--callout-primary-color) shrink-0 opacity-60"></div><div class="flex flex-col gap-2"><div class="callout__title flex items-center gap-2 font-semibold tracking-tight"><i class="callout__icon fa-triangle-exclamation leading-none text-(--callout-primary-color) text-sm shrink-0"></i> Warning</div><div class="callout__content markdown-body flex-1 min-w-0"><p>If someone is reading this blog, please be aware that the writer <strong>DID NOT</strong> consider the experience of the other readers.<br>After all, the most important thing is about writing things down for better memorization.</p></div></div></div><div class="callout callout--titled danger mb-4 rounded-small shadow-redefine-flat bg-(--callout-bg-color) p-3 pl-1 relative flex flex-row gap-2"><div role="none" class="rounded-full self-stretch w-0.5 bg-(--callout-primary-color) shrink-0 opacity-60"></div><div class="flex flex-col gap-2"><div class="callout__title flex items-center gap-2 font-semibold tracking-tight"><i class="callout__icon fa-triangle-exclamation leading-none text-(--callout-primary-color) text-sm shrink-0"></i> Warning</div><div class="callout__content markdown-body flex-1 min-w-0"><p>Starting from this blog, the lesson is updated to ICS2024.<br>Video link: <a class="link"   href="https://www.bilibili.com/video/BV1ztSiYPEr8/" >ICS2024-南京大学软件学院计算机系统基础实验<i class="fa-solid fa-arrow-up-right ml-[0.2em] font-light align-text-top text-[0.7em] link-icon"></i></a></p></div></div></div><h2 id="IO"><a href="#IO" class="headerlink" title="IO"></a>IO</h2><ul><li><p>The computer itself doesn’t pretty much do anything apart from ‘computing’. It is the IO devices that connect the computer with the outer world, allowing more application and wider promotion.</p></li><li><p>We included two instruction sets(on x86) <code>in</code> and <code>out</code> to handle IO operations. The first one is to read data from the devices into the cpu, and the latter one does the opposite.</p></li></ul><h3 id="Memory-Mapping-IO"><a href="#Memory-Mapping-IO" class="headerlink" title="Memory Mapping IO"></a>Memory Mapping IO</h3><ul><li>One very basic way of reading data from outer devices is that, we simply connect the cpu with the registers in the outer devices with circuits. Though being simple, it is obviously not a good way as the number of physical interface on cpu is limited, which means we can’t just design an interface for each of the possible device.</li></ul><hr><ul><li><p>That introduces the second way of connecting cpu to the outer world, <strong>memory mapped IO</strong>.</p></li><li><p>Memory mapped IO is basically saying that, we could set up a part of the memory for a specific outer device. When writing&#x2F;reading data from that part of memory, we are essentially reading data from the designated device.</p></li><li><p>The advantage of this way is that we avoid the problem that we couldn’t connect infinite devices to the cpu.</p></li><li><p>But the drawback is that a part of the memory is consumed for this mapping.</p></li></ul><hr><ul><li><p>Something worth mentioning is that, the compiler doesn’t actually know the writing memory section is mapped to a device or not. Sometimes it could do unwanted optimizations. Check the example down below:</p><div class="code-container" data-rel="C"><figure class="iseeu highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">void</span> <span class="title function_">foo</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">0</span>; i &lt; <span class="number">1024</span>; i++) &#123;</span><br><span class="line">        <span class="comment">// out(ADDR, 0)</span></span><br><span class="line">        (*(<span class="type">char</span> *)ADDR) = <span class="number">0</span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div><ul><li>Here we intended to write a <code>0</code> to <code>ADDR</code> for 1024 times. But the compiler does not know that this is a spectial section of memory which is mapped to a outer device, so it would consider no need to write the same value in repeatedly, you will end up only writing <code>0</code> one time to <code>ADDR</code>.</li><li>Specifically, the loop logic will be deserted with the compilation flag <code>-O2</code>(or higher optimization levels).</li></ul></li><li><p>The solution is to add <code>volatile</code> keyword, which prevent the compiler from optimizing the code.</p><div class="code-container" data-rel="C"><figure class="iseeu highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">(*(<span class="keyword">volatile</span> <span class="type">char</span> *)ADDR) = <span class="number">0</span>;</span><br></pre></td></tr></table></figure></div></li></ul><h2 id="Two-Special-IO-devices"><a href="#Two-Special-IO-devices" class="headerlink" title="Two Special IO devices"></a>Two Special IO devices</h2><h3 id="Bus"><a href="#Bus" class="headerlink" title="Bus"></a>Bus</h3><p>From <a class="link"   href="https://en.wikipedia.org/wiki/Bus_(computing)" >Wikipedia<i class="fa-solid fa-arrow-up-right ml-[0.2em] font-light align-text-top text-[0.7em] link-icon"></i></a>:</p><blockquote><p>In computer architecture, a bus is a communication system that transfers data between components inside a computer, or between computers. This expression covers all related hardware components (wire, optical fiber, etc.) and software, including communication protocols.</p></blockquote><ul><li>The bus connect the IO devices in the computer with the cpu.</li></ul><h3 id="Programmable-Interrupt-Controller-PIC"><a href="#Programmable-Interrupt-Controller-PIC" class="headerlink" title="Programmable Interrupt Controller(PIC)"></a>Programmable Interrupt Controller(PIC)</h3><p>From <a class="link"   href="https://en.wikipedia.org/wiki/Programmable_interrupt_controller" >Wikipedia<i class="fa-solid fa-arrow-up-right ml-[0.2em] font-light align-text-top text-[0.7em] link-icon"></i></a></p><blockquote><p>In computing, a programmable interrupt controller (PIC) is an integrated circuit that helps a microprocessor (or CPU) handle interrupt requests (IRQs) coming from multiple different sources (like external I&#x2F;O devices) which may occur simultaneously. It helps prioritize IRQs so that the CPU switches execution to the most appropriate interrupt handler (ISR) after the PIC assesses the IRQs’ relative priorities.</p></blockquote><ul><li>Generally speaking, PIC is a device which sends a interruption signal to the cpu at a fixed frequency. This allows interaction with the executing process, and furthur more, concurrency.</li></ul><h2 id="Interruption-Compensating-for-IO-Device-Speed-Deficiencies"><a href="#Interruption-Compensating-for-IO-Device-Speed-Deficiencies" class="headerlink" title="Interruption: Compensating for IO Device Speed Deficiencies"></a>Interruption: Compensating for IO Device Speed Deficiencies</h2><ul><li><p>Most interaction with I&#x2F;O devices are slow, as there might be interaction with the real world which are of course slower than the electronic world inside the cpu.</p></li><li><p>Because of that, we don’t want the cpu to wait until the IO jobs are done before executing other instructions.</p></li><li><p>This requires two things:</p><ol><li>After sending data&#x2F;instructions to I&#x2F;O devices, the cpu needs to turn to other tasks&#x2F;processes.</li><li>Once the I&#x2F;O jobs are done, the devices should inform the cpu to do the remaining jobs or retrieve the data from the devices.</li></ol></li><li><p>This way, the relatively slower I&#x2F;O operation could be executed without halting the cpu.</p></li></ul>]]>
    </content>
    <id>https://blog.imlast.top/2024/11/08/nju-pa-w8/</id>
    <link href="https://blog.imlast.top/2024/11/08/nju-pa-w8/"/>
    <published>2024-11-08T07:21:10.000Z</published>
    <summary>Notes taken for class W8 of NJU PA</summary>
    <title>PA W8 - IO Devices</title>
    <updated>2024-11-08T07:21:10.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>Last</name>
    </author>
    <category term="笔记" scheme="https://blog.imlast.top/categories/%E7%AC%94%E8%AE%B0/"/>
    <category term="c" scheme="https://blog.imlast.top/tags/c/"/>
    <category term="make" scheme="https://blog.imlast.top/tags/make/"/>
    <content>
      <![CDATA[<h2 id="General-Project-Structure"><a href="#General-Project-Structure" class="headerlink" title="General Project Structure"></a>General Project Structure</h2><ul><li><p>A general C project structure with a well-functioning Makefile, automated tests(unit test):</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line">.</span><br><span class="line">├── bin</span><br><span class="line">├── build</span><br><span class="line">│   ├── libYOUR_LIBRARY.a</span><br><span class="line">│   └── libYOUR_LIBRARY.so</span><br><span class="line">├── LICENSE</span><br><span class="line">├── Makefile</span><br><span class="line">├── README.md</span><br><span class="line">├── src</span><br><span class="line">│   ├── libex29.c</span><br><span class="line">│   └── libex29.o</span><br><span class="line">└── tests</span><br><span class="line">    ├── libex29_tests</span><br><span class="line">    ├── libex29_tests.c</span><br><span class="line">    ├── minunit.h</span><br><span class="line">    ├── runtests.sh</span><br><span class="line">    └── tests.log</span><br><span class="line"></span><br><span class="line">5 directories, 13 files</span><br></pre></td></tr></table></figure></div></li><li><p>For details inside of each file, see down below.</p></li></ul><h2 id="Makefile"><a href="#Makefile" class="headerlink" title="Makefile"></a>Makefile</h2><div class="code-container" data-rel="Makefile"><figure class="iseeu highlight makefile"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta"><span class="keyword">.PHONY</span>: all dev clean test</span></span><br><span class="line"></span><br><span class="line">CFLAGS = -g -O2 -Wall -Wextra -Isrc -rdynamic -DNDEBUG <span class="variable">$(OPTFLAGS)</span></span><br><span class="line">LIBS = -ldl <span class="variable">$(OPTLIBS)</span></span><br><span class="line">PREFIX ?= /usr/local</span><br><span class="line"></span><br><span class="line">SOURCES = <span class="variable">$(<span class="built_in">wildcard</span> src/**/*.c src/*.c)</span></span><br><span class="line">OBJECTS = <span class="variable">$(<span class="built_in">patsubst</span> %.c,%.o,<span class="variable">$(SOURCES)</span>)</span></span><br><span class="line"></span><br><span class="line">TEST_SRC = <span class="variable">$(<span class="built_in">wildcard</span> tests/*_tests.c)</span></span><br><span class="line">TESTS = <span class="variable">$(<span class="built_in">patsubst</span> %.c,%,<span class="variable">$(TEST_SRC)</span>)</span></span><br><span class="line"></span><br><span class="line">TARGET = build/libYOUR_LIBRARY.a</span><br><span class="line">SO_TARGET = <span class="variable">$(<span class="built_in">patsubst</span> %.a,%.so,<span class="variable">$(TARGET)</span>)</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># the target build</span></span><br><span class="line"><span class="section">all: <span class="variable">$(TARGET)</span> <span class="variable">$(SO_TARGET)</span> test</span></span><br><span class="line"></span><br><span class="line"><span class="section">dev: CFLAGS = -g -Wall -Isrc -Wall -Wextra <span class="variable">$(OPTFLAGS)</span></span></span><br><span class="line"><span class="section">dev: all</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># `-fPIC` means Position Independent Code, which is essential for creating</span></span><br><span class="line"><span class="comment"># shared libraries</span></span><br><span class="line"><span class="variable">$(TARGET)</span>: CFLAGS += -fPIC</span><br><span class="line"><span class="variable">$(TARGET)</span>: build <span class="variable">$(OBJECTS)</span></span><br><span class="line">4ar rcs <span class="variable">$@</span> <span class="variable">$(OBJECTS)</span></span><br><span class="line">4ranlib <span class="variable">$@</span></span><br><span class="line"></span><br><span class="line"><span class="variable">$(SO_TARGET)</span>: <span class="variable">$(TARGET)</span> <span class="variable">$(OBJECTS)</span></span><br><span class="line">4<span class="variable">$(CC)</span> -shared -o <span class="variable">$@</span> <span class="variable">$(OBJECTS)</span></span><br><span class="line"></span><br><span class="line"><span class="section">build:</span></span><br><span class="line">4@mkdir -p build</span><br><span class="line">4@mkdir -p bin</span><br><span class="line"></span><br><span class="line"><span class="comment"># unit test</span></span><br><span class="line"><span class="section">test: CFLAGS += <span class="variable">$(TARGET)</span></span></span><br><span class="line"><span class="section">test: <span class="variable">$(TESTS)</span></span></span><br><span class="line">4sh ./tests/runtests.sh</span><br><span class="line"></span><br><span class="line"><span class="section">valgrind:</span></span><br><span class="line">4VALGRIND=<span class="string">&quot;valgrind --log-file=/tmp/valgrind-%p.log&quot;</span> <span class="variable">$(MAKE)</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># cleaner</span></span><br><span class="line"><span class="section">clean:</span></span><br><span class="line">4rm -rf build <span class="variable">$(OBJECTS)</span> <span class="variable">$(TESTS)</span></span><br><span class="line">4rm -f tests/tests.log</span><br><span class="line">4find . -name <span class="string">&quot;*.gc&quot;</span> -exec rm &#123;&#125; \;</span><br><span class="line">4rm -rf `find . -name <span class="string">&quot;*.dSYM&quot;</span> -print`</span><br><span class="line"></span><br><span class="line"><span class="comment"># install</span></span><br><span class="line"><span class="section">install: all</span></span><br><span class="line">4install -d <span class="variable">$(DESTDIR)</span>/<span class="variable">$(PREFIX)</span>/lib/</span><br><span class="line">4install <span class="variable">$(TARGET)</span> <span class="variable">$(DESTDIR)</span>/<span class="variable">$(PREFIX)</span>/lib/</span><br><span class="line"></span><br><span class="line"><span class="comment"># checker</span></span><br><span class="line">BADFUNCS = [^_.&gt;a-zA-Z0-9](str(n?cpy|n?cat|xfrm|n?dup|str|pbrk|tok|_)|stpn?cpy|a?sn?printf|byte_)</span><br><span class="line"><span class="section">check:</span></span><br><span class="line">4@echo Files with potentially dangerous functions.</span><br><span class="line">4@grep -E <span class="string">&quot;<span class="variable">$(BADFUNCS)</span>&quot;</span> <span class="variable">$(SOURCES)</span> || true</span><br></pre></td></tr></table></figure></div><ul><li><p>This is quite a large Makefile, containing mechanisms which allow automatic build and test process.</p></li><li><p>In this blog, I will only focus on the important parts as I am too lazy to explain it thoroughly. It’s just doesn’t worth it.</p></li></ul><h3 id="Main-Logic"><a href="#Main-Logic" class="headerlink" title="Main Logic"></a>Main Logic</h3><ul><li>This Makefile automatically collects all C source files from the <code>src/</code> directory and its subdirectories, compiles them into object files, and bundles them into both static (.a) and shared (.so) library formats while also setting up a testing framework - all orchestrated through a hierarchy of targets where <code>all</code> builds everything.</li><li><code>dev</code> provides a debug-friendly version, and <code>clean</code> removes all generated files.</li></ul><h3 id="Tests"><a href="#Tests" class="headerlink" title="Tests"></a>Tests</h3><ul><li><p>This Makefile contains logic for automatic tests, right after the build process.</p></li><li><p>The test process is actually written in <code>tests/runtests.sh</code>:</p><div class="code-container" data-rel="Bash"><figure class="iseeu highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># color definition(see folder down below)</span></span><br><span class="line"><span class="comment"># ...</span></span><br><span class="line"></span><br><span class="line"><span class="built_in">echo</span> -e <span class="string">&quot;<span class="variable">$&#123;BG_BLUE&#125;</span><span class="variable">$&#123;FG_ORANGE&#125;</span> Running unit tests:<span class="variable">$&#123;NONE&#125;</span>&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">for</span> i <span class="keyword">in</span> tests/*_tests</span><br><span class="line"><span class="keyword">do</span></span><br><span class="line">    <span class="keyword">if</span> <span class="built_in">test</span> -f <span class="variable">$i</span>; <span class="keyword">then</span></span><br><span class="line">        <span class="keyword">if</span> <span class="variable">$VALGRIND</span> ./<span class="variable">$i</span> 2 &gt;&gt; tests/tests.log; <span class="keyword">then</span></span><br><span class="line">            <span class="built_in">echo</span> -e <span class="string">&quot;<span class="variable">$&#123;FG_GREEN&#125;</span> <span class="variable">$i</span> PASS<span class="variable">$&#123;NONE&#125;</span>&quot;</span></span><br><span class="line">        <span class="keyword">else</span></span><br><span class="line">            <span class="built_in">echo</span> -e <span class="string">&quot;<span class="variable">$&#123;FG_RED&#125;</span>ERROR in test <span class="variable">$i</span>:<span class="variable">$&#123;NONE&#125;</span>&quot;</span></span><br><span class="line">            <span class="built_in">echo</span> -e <span class="string">&quot;<span class="variable">$&#123;FG_RED&#125;</span>-----<span class="variable">$&#123;NONE&#125;</span>&quot;</span></span><br><span class="line">            <span class="built_in">tail</span> tests/tests.log</span><br><span class="line">            <span class="built_in">exit</span> 1</span><br><span class="line">        <span class="keyword">fi</span></span><br><span class="line">    <span class="keyword">fi</span></span><br><span class="line"><span class="keyword">done</span></span><br><span class="line"></span><br><span class="line"><span class="built_in">echo</span> <span class="string">&quot;&quot;</span></span><br></pre></td></tr></table></figure></div></li></ul><details class="relative my-4 border border-border-color bg-second-background-color rounded-md  blue" data-header-exclude><summary class="px-4 py-2 rounded-md shadow-[0_0_2px_0_var(--shadow-color-1)] cursor-pointer not-markdown"><i class="fa-solid fa-chevron-right"></i>Color_Definition</summary><div class="content p-4 "><div class="code-container" data-rel="Bash"><figure class="iseeu highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># Foreground colors</span></span><br><span class="line">FG_BLACK=<span class="string">&quot;\033[1;30m&quot;</span></span><br><span class="line">FG_RED=<span class="string">&quot;\033[1;31m&quot;</span></span><br><span class="line">FG_GREEN=<span class="string">&quot;\033[1;32m&quot;</span></span><br><span class="line">FG_YELLOW=<span class="string">&quot;\033[1;33m&quot;</span></span><br><span class="line">FG_ORANGE=<span class="string">&quot;\033[44;93m&quot;</span></span><br><span class="line">FG_BLUE=<span class="string">&quot;\033[1;34m&quot;</span></span><br><span class="line">FG_MAGENTA=<span class="string">&quot;\033[1;35m&quot;</span></span><br><span class="line">FG_CYAN=<span class="string">&quot;\033[1;36m&quot;</span></span><br><span class="line">FG_WHITE=<span class="string">&quot;\033[1;37m&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># Background colors</span></span><br><span class="line">BG_BLACK=<span class="string">&quot;\033[1;40m&quot;</span></span><br><span class="line">BG_RED=<span class="string">&quot;\033[1;41m&quot;</span></span><br><span class="line">BG_GREEN=<span class="string">&quot;\033[1;42m&quot;</span></span><br><span class="line">BG_YELLOW=<span class="string">&quot;\033[1;43m&quot;</span></span><br><span class="line">BG_ORANGE=<span class="string">&quot;\033[48;93m&quot;</span></span><br><span class="line">BG_BLUE=<span class="string">&quot;\033[1;44m&quot;</span></span><br><span class="line">BG_MAGENTA=<span class="string">&quot;\033[1;45m&quot;</span></span><br><span class="line">BG_CYAN=<span class="string">&quot;\033[1;46m&quot;</span></span><br><span class="line">BG_WHITE=<span class="string">&quot;\033[1;47m&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># Reset</span></span><br><span class="line">NONE=<span class="string">&quot;\033[0m&quot;</span></span><br></pre></td></tr></table></figure></div><p><em>Stolen from <code>debug.h</code> from PA :)</em></p></div></details><div class="callout callout--titled info mb-4 rounded-small shadow-redefine-flat bg-(--callout-bg-color) p-3 pl-1 relative flex flex-row gap-2"><div role="none" class="rounded-full self-stretch w-0.5 bg-(--callout-primary-color) shrink-0 opacity-60"></div><div class="flex flex-col gap-2"><div class="callout__title flex items-center gap-2 font-semibold tracking-tight"><i class="callout__icon fa-circle-info leading-none text-(--callout-primary-color) text-sm shrink-0"></i> Note:</div><div class="callout__content markdown-body flex-1 min-w-0"><p>Variable <code>$VALGRIND</code> in the script should be defined in the Makefile when running <code>make valgrind</code>.</p><p>When running <code>make</code> or <code>make test</code>, <code>$VALGRIND</code> will be empty&#x2F;undefined, so the command simply runs <code>./$i</code>.</p><hr><p>Also, the <code>2</code> in line <code>if $VALGRIND ./$i 2 &gt;&gt; tests/tests.log; then</code> refers to file descriptor 2, which is <strong>stderr(standard error)</strong>.</p><blockquote><p>In Unix&#x2F;Linux systems, there are three standard file descriptors:</p><p>0: stdin (standard input)<br>1: stdout (standard output)<br>2: stderr (standard error)</p></blockquote><p>So <code>2 &gt;&gt;</code> means “append stderr output to the specified file”. This ensures that error messages and debugging information (which typically go to stderr) are captured in tests&#x2F;tests.log.</p></div></div></div><h2 id="Debug-Helper"><a href="#Debug-Helper" class="headerlink" title="Debug Helper"></a>Debug Helper</h2><details class="relative my-4 border border-border-color bg-second-background-color rounded-md  blue" data-header-exclude><summary class="px-4 py-2 rounded-md shadow-[0_0_2px_0_var(--shadow-color-1)] cursor-pointer not-markdown"><i class="fa-solid fa-chevron-right"></i>dbg.h/log.h</summary><div class="content p-4 "><p><code>dbg.h</code>:</p><div class="code-container" data-rel="C"><figure class="iseeu highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">ifndef</span> __DBG_H__</span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> __DBG_H__</span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&quot;log.h&quot;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;errno.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;stdio.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;string.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="keyword">ifdef</span> NDEBUG</span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> debug(M, ...)</span></span><br><span class="line"><span class="meta">#<span class="keyword">else</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> debug(M, ...)                                                          \</span></span><br><span class="line"><span class="meta">    fprintf(stderr, ANSI_FMT(<span class="string">&quot;DEBUG %s:%d: &quot;</span> M <span class="string">&quot;\n&quot;</span>, ANSI_BG_BLACK), __FILE__, \</span></span><br><span class="line"><span class="meta">            __LINE__, ##__VA_ARGS__)</span></span><br><span class="line"><span class="meta">#<span class="keyword">endif</span> <span class="comment">// DEBUG</span></span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> clean_errno() (errno == 0 ? <span class="string">&quot;None&quot;</span> : strerror(errno))</span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> log_err(M, ...)                                                        \</span></span><br><span class="line"><span class="meta">    fprintf(stderr,                                                            \</span></span><br><span class="line"><span class="meta">            ANSI_FMT(<span class="string">&quot;[ERROR] (%s:%d: errno: %s) &quot;</span> M <span class="string">&quot;\n&quot;</span>, ANSI_FG_RED),       \</span></span><br><span class="line"><span class="meta">            __FILE__, __LINE__, clean_errno(), ##__VA_ARGS__)</span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> log_warn(M, ...)                                                       \</span></span><br><span class="line"><span class="meta">    fprintf(stderr,                                                            \</span></span><br><span class="line"><span class="meta">            ANSI_FMT(<span class="string">&quot;[WARN] (%s:%d: errno: %s) &quot;</span> M <span class="string">&quot;\n&quot;</span>, ANSI_FG_YELLOW),     \</span></span><br><span class="line"><span class="meta">            __FILE__, __LINE__, clean_errno(), ##__VA_ARGS__)</span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> log_info(M, ...)                                                       \</span></span><br><span class="line"><span class="meta">    fprintf(stderr, ANSI_FMT(<span class="string">&quot;[INFO] (%s:%d:) &quot;</span> M <span class="string">&quot;\n&quot;</span>, ANSI_FG_BLUE),         \</span></span><br><span class="line"><span class="meta">            __FILE__, __LINE__, ##__VA_ARGS__)</span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> check(A, M, ...)                                                       \</span></span><br><span class="line"><span class="meta">    <span class="keyword">if</span> (!(A)) &#123;                                                                \</span></span><br><span class="line"><span class="meta">        log_err(M, ##__VA_ARGS__);                                             \</span></span><br><span class="line"><span class="meta">        errno = 0;                                                             \</span></span><br><span class="line"><span class="meta">        goto <span class="keyword">error</span>;                                                            \</span></span><br><span class="line"><span class="meta">    &#125;</span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> sentinel(M, ...)                                                       \</span></span><br><span class="line"><span class="meta">    &#123;                                                                          \</span></span><br><span class="line"><span class="meta">        log_err(M, ##__VA_ARGS__);                                             \</span></span><br><span class="line"><span class="meta">        errno = 0;                                                             \</span></span><br><span class="line"><span class="meta">        goto <span class="keyword">error</span>;                                                            \</span></span><br><span class="line"><span class="meta">    &#125;</span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> check_mem(A) check((A), <span class="string">&quot;Out of memory.&quot;</span>)</span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> check_debug(A, M, ...)                                                 \</span></span><br><span class="line"><span class="meta">    <span class="keyword">if</span> (!(A)) &#123;                                                                \</span></span><br><span class="line"><span class="meta">        debug(M, ##__VA_ARGS__);                                               \</span></span><br><span class="line"><span class="meta">        errno = 0;                                                             \</span></span><br><span class="line"><span class="meta">        goto <span class="keyword">error</span>;                                                            \</span></span><br><span class="line"><span class="meta">    &#125;</span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="keyword">endif</span> <span class="comment">// !__DBG_H__</span></span></span><br></pre></td></tr></table></figure></div><p><code>log.h</code>:</p><div class="code-container" data-rel="C"><figure class="iseeu highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">ifndef</span> __LOG_H__</span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> __LOG_H__</span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> ANSI_FG_BLACK <span class="string">&quot;\33[1;30m&quot;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> ANSI_FG_RED <span class="string">&quot;\33[1;31m&quot;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> ANSI_FG_GREEN <span class="string">&quot;\33[1;32m&quot;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> ANSI_FG_YELLOW <span class="string">&quot;\33[1;33m&quot;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> ANSI_FG_BLUE <span class="string">&quot;\33[1;34m&quot;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> ANSI_FG_MAGENTA <span class="string">&quot;\33[1;35m&quot;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> ANSI_FG_CYAN <span class="string">&quot;\33[1;36m&quot;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> ANSI_FG_WHITE <span class="string">&quot;\33[1;37m&quot;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> ANSI_BG_BLACK <span class="string">&quot;\33[1;40m&quot;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> ANSI_BG_RED <span class="string">&quot;\33[1;41m&quot;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> ANSI_BG_GREEN <span class="string">&quot;\33[1;42m&quot;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> ANSI_BG_YELLOW <span class="string">&quot;\33[1;43m&quot;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> ANSI_BG_BLUE <span class="string">&quot;\33[1;44m&quot;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> ANSI_BG_MAGENTA <span class="string">&quot;\33[1;35m&quot;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> ANSI_BG_CYAN <span class="string">&quot;\33[1;46m&quot;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> ANSI_BG_WHITE <span class="string">&quot;\33[1;47m&quot;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> ANSI_NONE <span class="string">&quot;\33[0m&quot;</span></span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> ANSI_FMT(str, fmt) fmt str ANSI_NONE</span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="keyword">endif</span> <span class="comment">// !__LOG_H__</span></span></span><br></pre></td></tr></table></figure></div></div></details><h2 id="To-Be-Continued…"><a href="#To-Be-Continued…" class="headerlink" title="To Be Continued…"></a>To Be Continued…</h2>]]>
    </content>
    <id>https://blog.imlast.top/2024/10/30/Learn-C-the-Hard-Way/</id>
    <link href="https://blog.imlast.top/2024/10/30/Learn-C-the-Hard-Way/"/>
    <published>2024-10-30T09:14:52.000Z</published>
    <summary>
      <![CDATA[Interesting & useful functions or header files from _Learn C the Hard Way_]]>
    </summary>
    <title>Learn C the Hard Way</title>
    <updated>2024-10-30T09:14:52.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>Last</name>
    </author>
    <category term="笔记" scheme="https://blog.imlast.top/categories/%E7%AC%94%E8%AE%B0/"/>
    <category term="c" scheme="https://blog.imlast.top/tags/c/"/>
    <category term="pa" scheme="https://blog.imlast.top/tags/pa/"/>
    <content>
      <![CDATA[<div class="callout callout--titled warning mb-4 rounded-small shadow-redefine-flat bg-(--callout-bg-color) p-3 pl-1 relative flex flex-row gap-2"><div role="none" class="rounded-full self-stretch w-0.5 bg-(--callout-primary-color) shrink-0 opacity-60"></div><div class="flex flex-col gap-2"><div class="callout__title flex items-center gap-2 font-semibold tracking-tight"><i class="callout__icon fa-triangle-exclamation leading-none text-(--callout-primary-color) text-sm shrink-0"></i> Warning</div><div class="callout__content markdown-body flex-1 min-w-0"><p>If someone is reading this blog, please be aware that the writer <strong>DID NOT</strong> consider the experience of the other readers.<br>After all, the most important thing is about writing things down for better memorization.</p></div></div></div><h2 id="Bit-Operation-Macros"><a href="#Bit-Operation-Macros" class="headerlink" title="Bit Operation Macros"></a>Bit Operation Macros</h2><ul><li>Bit operations are usually not very readable to human, so I decided to write down the explanations that I come up with here.</li></ul><h3 id="BITS-BITMASK"><a href="#BITS-BITMASK" class="headerlink" title="BITS &amp;&amp; BITMASK"></a><em>BITS</em> &amp;&amp; <em>BITMASK</em></h3><div class="code-container" data-rel="C"><figure class="iseeu highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">define</span> BITMASK(bits) ((1ull &lt;&lt; (bits)) - 1)</span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> BITS(x, hi, lo)                                                        \</span></span><br><span class="line"><span class="meta">    (((x) &gt;&gt; (lo)) &amp; BITMASK((hi) - (lo) + 1)) <span class="comment">// similar to x[hi:lo] in verilog</span></span></span><br></pre></td></tr></table></figure></div><ul><li>These two macros are used to extract some of the bits from a binary number.</li><li>Specifically, they are used to extract the ‘immediate’ from the instructions.</li></ul><hr><ul><li><p>First we take a look at <code>BITMASK</code>:</p><ul><li><p>This macro creates a bitmask of a specified number of bits.</p></li><li><p>For example, <code>BITMASK(3)</code> would create a bitmask of 3 bits: <code>0000...0111</code>, which is the number <code>7</code>.</p></li><li><p>The macro works by shifting the number <code>1</code> to the left by bits positions and then subtracting <code>1</code> from the result:</p><ul><li><code>1</code> -&gt; <code>0000...1000</code> (shift <code>1</code> to the left by 3 bits)</li><li><code>0000...1000</code> -&gt; <code>0000...0111</code> (subtract <code>1</code> from the result)</li></ul></li><li><p><em>NB: <code>1ull</code> stands for ‘unsigned long long integer’. The suffix ‘ull’ here is used to make the number <code>1</code> 64-bit</em></p></li></ul></li><li><p>Then <code>BITS</code>:</p><ul><li><p>This macro extracts bits from position <code>lo</code> to position <code>hi</code> from the value <code>x</code>.</p></li><li><p>It does it by performing several bit operations:</p><ul><li>First it shifts <code>x</code> to the right by <code>lo</code> positions, which effectively removes the bits lower than the bit on <code>lo</code> position.</li><li>Second it masks <code>x</code> with the bitmask created by the macro explained above to wipe out all values higher than position <code>hi</code> of the original <code>x</code>.</li></ul></li></ul></li></ul><h3 id="SEXT"><a href="#SEXT" class="headerlink" title="SEXT"></a><em>SEXT</em></h3><div class="code-container" data-rel="C"><figure class="iseeu highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">define</span> SEXT(x, len)                                                           \</span></span><br><span class="line"><span class="meta">    (&#123;                                                                         \</span></span><br><span class="line"><span class="meta">        struct &#123;                                                               \</span></span><br><span class="line"><span class="meta">            int64_t n : len;                                                   \</span></span><br><span class="line"><span class="meta">        &#125; __x = &#123;.n = x&#125;;                                                      \</span></span><br><span class="line"><span class="meta">        (uint64_t) __x.n;                                                      \</span></span><br><span class="line"><span class="meta">    &#125;)</span></span><br></pre></td></tr></table></figure></div><ul><li><p><code>SEXT</code> stands for <strong>Sign Extension</strong>, which is the process of extending the sign bit of a binary number when moving it to a larger bit width.</p></li><li><p>In the macro, the expression uses a bit field within a struct to define a signed integer of length <code>len</code> bits. The bit field ensures that the value of <code>x</code> is treated as a signed integer of that specific length.</p></li><li><p>The struct with the bit field automatically sign-extends the value when it is assigned to the <code>int64_t</code> member. The result is then cast back to <code>uint64_t</code> to maintain the full precision of the extended value.</p></li></ul><div class="callout callout--titled danger mb-4 rounded-small shadow-redefine-flat bg-(--callout-bg-color) p-3 pl-1 relative flex flex-row gap-2"><div role="none" class="rounded-full self-stretch w-0.5 bg-(--callout-primary-color) shrink-0 opacity-60"></div><div class="flex flex-col gap-2"><div class="callout__title flex items-center gap-2 font-semibold tracking-tight"><i class="callout__icon fa-triangle-exclamation leading-none text-(--callout-primary-color) text-sm shrink-0"></i> Note</div><div class="callout__content markdown-body flex-1 min-w-0"><p>The second parameter <code>len</code> of <code>SEXT</code> macro means <strong>the number of bits to treat <code>x</code> as for sign extension</strong>, instead of the target length.</p></div></div></div><hr><ul><li><p>The reason why sign extension is needed is that, the offset(immediate) is a signed value and <strong>it’s length is less than 64 bits</strong>(well of course since the length of the whole instruction is merely 32 bits). If we directly store it in a 64-bit variable, <strong>the higher bits are filled with zeros by default</strong>.</p><ul><li><p>Problem that sign extension resolves:</p></li><li><p>If we have a negative offset(immediate) presented as <strong>two’s complement</strong>, which means that it’s higher bits should all be <code>1</code>.</p></li><li><p>Now if we store it directly into a 64-bit variable, the higher bits are becoming <code>0</code>s, thus making the variable a positive one.</p></li><li><p>Example:</p><ul><li><strong>Original 8-bit Signed Value</strong>: Let’s say you have the binary value <code>11111111</code> (which is <code>-1</code> in two’s complement notation when interpreted as a 8-bit signed integer).</li><li><strong>Direct Storage Without Sign Extension</strong>: If you store this 8-bit value directly into a 32-bit variable without sign extension, it would look like this: <code>00000000 00000000 00000000 11111111</code> (which is <code>255</code> in decimal, a positive value).</li></ul></li></ul></li></ul><h2 id="Casts-and-Conventions-Ensuring-Proper-Sign-Extension-in-C"><a href="#Casts-and-Conventions-Ensuring-Proper-Sign-Extension-in-C" class="headerlink" title="Casts and Conventions: Ensuring Proper Sign Extension in C"></a>Casts and Conventions: Ensuring Proper Sign Extension in C</h2><ul><li><p>In the implementation of instruction <code>mulh</code>, I encountered a suttle issue deeply rooted in C’s type conversion conventions.</p></li><li><p>RISC-V manual defines <code>mulh</code> as follows:</p><blockquote><p>MUL performs an XLEN-bit×XLEN-bit multiplication of rs1 by rs2 and places the lower XLEN bits<br>in the destination register. MULH, MULHU, and MULHSU perform the same multiplication but return the upper XLEN bits of the full 2×XLEN-bit product, for signed×signed, unsigned×unsigned,<br>and signed rs1×unsigned rs2 multiplication, respectively.</p></blockquote></li><li><p>Essentially, it’s just an extended version <code>mul</code>, designed for caculating larger numbers with lower precision.</p></li></ul><hr><ul><li><p>My first implementation looks like this:</p><div class="code-container" data-rel="C"><figure class="iseeu highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">INSTPAT(...);</span><br><span class="line">INSTPAT(<span class="string">&quot;0000001 ????? ????? 001 ????? 01100 11&quot;</span>, mulh, R,</span><br><span class="line">        R(rd) = ((<span class="type">int64_t</span>)src1 * (<span class="type">int64_t</span>)src2) &gt;&gt; <span class="number">32</span>);</span><br><span class="line">INSTPAT(...);</span><br></pre></td></tr></table></figure></div></li><li><p>During the test of <code>mul-longlong.c</code>, I used difftest to locate the problem is at <code>pc = 0x800000a0</code>, which is:</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">800000a0:402fc17b3          mulha5,s8,a5</span><br></pre></td></tr></table></figure></div></li><li><p>The difftest showed a discrepancy:</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">[src/isa/riscv32/difftest/dut.c:23 isa_difftest_checkregs] a5 is different after executing instruction at pc = 0x800000a0, right = 0x19d29ab9, wrong = 0x7736200d, diff = 0x6ee4bab4</span><br></pre></td></tr></table></figure></div></li></ul><hr><ul><li><p>At first glance, the logic written in <code>inst.c</code> seemed correct:</p><ol><li>convert the value stored in the two registers to signed intergers.</li><li>multiply them together, then shift right for 32 bits to get the upper 32 bits, and then store the result in the destination register.</li></ol></li><li><p>It turns out that the problem rooted in the conversion process.</p></li></ul><hr><ul><li><p>In C, when converting a smaller signed type to a larger signed type, for example, casting a 32-bit signed integer(<code>int32_t</code>) to a 64-bit signed integer(<code>int64_t</code>), C correctly handles the sign extension, preserving the value after the conversion.</p></li><li><p>The same thing happens when we convert an unsigned type to a same size signed type, C will do the sign extension automatically as well.</p></li><li><p>But, if we convert a smaller unsigned type, to a larger signed type, C does not automatically perform sign extension, which leads to the problem that I was facing with.</p></li><li><p>Consider this simple example program:</p><div class="code-container" data-rel="C"><figure class="iseeu highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;stdint.h&gt;</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string">&lt;stdio.h&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="type">int</span> <span class="title function_">main</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="comment">// Max value for 32-bit unsigned integer</span></span><br><span class="line">    <span class="type">uint32_t</span> u32 = <span class="number">0xaeb1c2aa</span>;</span><br><span class="line">    <span class="comment">// Cast to a 64-bit signed integer</span></span><br><span class="line">    <span class="type">int64_t</span> s64 = (<span class="type">int64_t</span>)u32;</span><br><span class="line"></span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">&quot;u32:  %u\n&quot;</span>, u32);</span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">&quot;s32:  %d\n&quot;</span>, (<span class="type">int32_t</span>)u32);</span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">&quot;directly converted s64:  %ld\n&quot;</span>, s64);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// Max value for 32-bit unsigned integer</span></span><br><span class="line">    <span class="type">uint32_t</span> u32_2 = <span class="number">0xaeb1c2aa</span>;</span><br><span class="line">    <span class="comment">// Cast to a 64-bit signed integer with sign extension</span></span><br><span class="line">    <span class="type">int64_t</span> s64_2 = (<span class="type">int64_t</span>)(<span class="type">int32_t</span>)u32;</span><br><span class="line">    <span class="built_in">printf</span>(<span class="string">&quot;expected s64:  %ld\n&quot;</span>, s64_2);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div><p>output:</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">u32:  2930885290</span><br><span class="line">s32:  -1364082006</span><br><span class="line">directly converted s64:  2930885290</span><br><span class="line">expected s64:  -1364082006</span><br></pre></td></tr></table></figure></div></li><li><p>From the example above we can see that: <strong>when directly convert a 32-bit unsigned integer to a 64-bit signed integer, C does not automatically perform sign extension.</strong></p></li><li><p>That is the reason why my first implementation for instruction <code>mulh</code> was incorrect: <strong>it did not conduct sign conversion</strong>.</p></li></ul><hr><ul><li><p>The solution is simple, first convert the value into a 32-bit signed int, then convert it into a 64-bit signed one:</p><div class="code-container" data-rel="C"><figure class="iseeu highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">INSTPAT(<span class="string">&quot;0000001 ????? ????? 001 ????? 01100 11&quot;</span>, mulh, R,</span><br><span class="line">        R(rd) = ((<span class="type">int64_t</span>)(<span class="type">int32_t</span>)src1 * (<span class="type">int64_t</span>)(<span class="type">int32_t</span>)src2) &gt;&gt; <span class="number">32</span>);</span><br></pre></td></tr></table></figure></div></li></ul><hr><h3 id="UPDATE"><a href="#UPDATE" class="headerlink" title="UPDATE"></a>UPDATE</h3><ul><li><p>Though the notes shown above are correct, it’s more likely that the course wants us to use the <code>SEXT</code> macro for sign extension.</p></li><li><p>The final version of <code>mulh</code> looks like this:</p><div class="code-container" data-rel="C"><figure class="iseeu highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">INSTPAT(<span class="string">&quot;0000001 ????? ????? 001 ????? 01100 11&quot;</span>, mulh, R,</span><br><span class="line">        R(rd) = (SEXT(src1, <span class="number">32</span>) * SEXT(src2, <span class="number">32</span>)) &gt;&gt; <span class="number">32</span>);</span><br></pre></td></tr></table></figure></div></li><li><p><code>SEXT</code> treats <code>src1</code> as a 32-bit value(<code>uint32_t</code>), and sign-extends it to 64 bits.</p></li></ul><h2 id="Cross-Compilation-Env"><a href="#Cross-Compilation-Env" class="headerlink" title="Cross Compilation Env"></a>Cross Compilation Env</h2><ul><li><p>When testing the implemented instructions, we would need to run tests in <code>am-kernels/tests/cpu-tests/tests/</code>.</p></li><li><p>The execution command is(take <code>dummy.c</code> as example):</p><div class="code-container" data-rel="Bash"><figure class="iseeu highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">make ARCH=<span class="variable">$ISA</span>-nemu ALL=dummy run</span><br></pre></td></tr></table></figure></div></li><li><p>However, my machine(cpu) is of x86 architecture, which means that it can not compile source code to riscv32 executables directly.</p></li><li><p>We would need the following packages to perform the compilation:</p><div class="code-container" data-rel="Bash"><figure class="iseeu highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">sudo</span> pacman -Sy riscv64-linux-gnu-gcc</span><br></pre></td></tr></table></figure></div></li></ul><hr><ul><li><p>Though still some errors may occur:</p><details class="relative my-4 border border-border-color bg-second-background-color rounded-md  orange" data-header-exclude><summary class="px-4 py-2 rounded-md shadow-[0_0_2px_0_var(--shadow-color-1)] cursor-pointer not-markdown"><i class="fa-solid fa-chevron-right"></i>Possible Compilation Error</summary><div class="content p-4 "><ol><li><code>wordsize.h</code> :</li></ol><div class="code-container" data-rel="Bash"><figure class="iseeu highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">/usr/riscv64-linux-gnu/include/bits/wordsize.h:28:3: error: <span class="comment">#error &quot;rv32i-based targets are not supported&quot;</span></span><br></pre></td></tr></table></figure></div><pre><code>- **solution**: modify `/usr/riscv64-linux-gnu/include/bits/wordsize.h` :![diff_1](https://s2.loli.net/2024/09/11/TLHS93vWhu4dXtG.png)</code></pre><ol start="2"><li><code>stubs.h</code> :</li></ol><div class="code-container" data-rel="Bash"><figure class="iseeu highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">/usr/riscv64-linux-gnu/include/gnu/stubs.h:8:11: fatal error: gnu/stubs-ilp32.h: No such file or directory</span><br></pre></td></tr></table></figure></div><pre><code>- **solution**: modify `/usr/riscv64-linux-gnu/include/gnu/stubs.h` :![diff_2](https://s2.loli.net/2024/09/11/21xQnpzPo796sL4.png)</code></pre></div></details></li></ul><h2 id="Weird-int-parameter-of-memset"><a href="#Weird-int-parameter-of-memset" class="headerlink" title="Weird int parameter of memset"></a>Weird <code>int</code> parameter of <code>memset</code></h2><ul><li><p>When implementing our own lib utils, I encountered a little problem in <code>memset</code>.</p></li><li><p>The first version of my <code>memset</code> looks like this:</p><div class="code-container" data-rel="C"><figure class="iseeu highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">void</span> *<span class="title function_">memset</span><span class="params">(<span class="type">void</span> *s, <span class="type">int</span> c, <span class="type">size_t</span> n)</span> &#123;</span><br><span class="line">    <span class="type">size_t</span> *p = s;</span><br><span class="line">    <span class="keyword">for</span> (<span class="type">size_t</span> i = <span class="number">0</span>; i &lt; n; i++) &#123;</span><br><span class="line">        *p = c;</span><br><span class="line">        p++;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> s;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div></li><li><p>The problem is that, the size of the memory which would be set by this function is of unit <strong>byte</strong>. However, here in my implemtation, the unit is <strong>int</strong>.</p></li><li><p>The correct version is:</p><div class="code-container" data-rel="C"><figure class="iseeu highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">void</span> *<span class="title function_">memset</span><span class="params">(<span class="type">void</span> *s, <span class="type">int</span> c, <span class="type">size_t</span> n)</span> &#123;</span><br><span class="line">    <span class="type">unsigned</span> <span class="type">char</span> *p = s;</span><br><span class="line">    <span class="keyword">for</span> (<span class="type">size_t</span> i = <span class="number">0</span>; i &lt; n; i++) &#123;</span><br><span class="line">        *p = (<span class="type">unsigned</span> <span class="type">char</span>)c;</span><br><span class="line">        p++;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> s;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div></li><li><p>We use <code>unsigned char</code> to represent a <strong>byte</strong> in memory, as it’s size is exactly one byte.</p></li><li><p>As for the reason of using <code>unsigned</code>, it is because of that we want to eliminate the ambiguity of the various implemetation of signed char.</p></li></ul><h2 id="Parsing-ELF"><a href="#Parsing-ELF" class="headerlink" title="Parsing ELF"></a>Parsing ELF</h2><ul><li>Parsing ELF files is the most difficult part in <code>ftrace</code> development. I knew nothing about the elf file before reading the man page of <code>elf</code> for two afternoons.</li><li>Here I recorded some of the most complicated part of the development for future review.</li></ul><hr><p>Wikipedia:</p><blockquote><p>In computing, the <strong>Executable and Linkable Format</strong>(<strong>ELF</strong>, formerly named Extensible Linking Format) is a common standard file format for executable files, object code, shared libraries, and core dumps.</p></blockquote><ul><li>We can use <code>readelf -a</code> to extract the content stored in an ELF file.</li><li>Take a <code>hello.c</code> as an example:</li></ul><details class="relative my-4 border border-border-color bg-second-background-color rounded-md  blue" data-header-exclude><summary class="px-4 py-2 rounded-md shadow-[0_0_2px_0_var(--shadow-color-1)] cursor-pointer not-markdown"><i class="fa-solid fa-chevron-right"></i>readelf</summary><div class="content p-4 "><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br><span class="line">141</span><br><span class="line">142</span><br><span class="line">143</span><br><span class="line">144</span><br><span class="line">145</span><br><span class="line">146</span><br><span class="line">147</span><br><span class="line">148</span><br><span class="line">149</span><br><span class="line">150</span><br><span class="line">151</span><br><span class="line">152</span><br><span class="line">153</span><br><span class="line">154</span><br><span class="line">155</span><br><span class="line">156</span><br><span class="line">157</span><br><span class="line">158</span><br><span class="line">159</span><br><span class="line">160</span><br><span class="line">161</span><br><span class="line">162</span><br><span class="line">163</span><br><span class="line">164</span><br><span class="line">165</span><br><span class="line">166</span><br><span class="line">167</span><br><span class="line">168</span><br><span class="line">169</span><br><span class="line">170</span><br><span class="line">171</span><br><span class="line">172</span><br><span class="line">173</span><br><span class="line">174</span><br><span class="line">175</span><br><span class="line">176</span><br><span class="line">177</span><br><span class="line">178</span><br><span class="line">179</span><br><span class="line">180</span><br><span class="line">181</span><br><span class="line">182</span><br><span class="line">183</span><br><span class="line">184</span><br><span class="line">185</span><br><span class="line">186</span><br><span class="line">187</span><br><span class="line">188</span><br><span class="line">189</span><br><span class="line">190</span><br><span class="line">191</span><br><span class="line">192</span><br><span class="line">193</span><br><span class="line">194</span><br><span class="line">195</span><br><span class="line">196</span><br><span class="line">197</span><br><span class="line">198</span><br><span class="line">199</span><br><span class="line">200</span><br><span class="line">201</span><br><span class="line">202</span><br><span class="line">203</span><br><span class="line">204</span><br><span class="line">205</span><br><span class="line">206</span><br><span class="line">207</span><br><span class="line">208</span><br><span class="line">209</span><br><span class="line">210</span><br><span class="line">211</span><br><span class="line">212</span><br><span class="line">213</span><br><span class="line">214</span><br><span class="line">215</span><br><span class="line">216</span><br><span class="line">217</span><br><span class="line">218</span><br><span class="line">219</span><br><span class="line">220</span><br><span class="line">221</span><br><span class="line">222</span><br><span class="line">223</span><br><span class="line">224</span><br><span class="line">225</span><br><span class="line">226</span><br><span class="line">227</span><br><span class="line">228</span><br><span class="line">229</span><br><span class="line">230</span><br><span class="line">231</span><br><span class="line">232</span><br><span class="line">233</span><br><span class="line">234</span><br><span class="line">235</span><br><span class="line">236</span><br><span class="line">237</span><br><span class="line">238</span><br><span class="line">239</span><br><span class="line">240</span><br><span class="line">241</span><br><span class="line">242</span><br><span class="line">243</span><br><span class="line">244</span><br><span class="line">245</span><br><span class="line">246</span><br><span class="line">247</span><br><span class="line">248</span><br><span class="line">249</span><br><span class="line">250</span><br></pre></td><td class="code"><pre><span class="line"> readelf -a hello</span><br><span class="line">ELF Header:</span><br><span class="line">Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00</span><br><span class="line">Class: ELF64</span><br><span class="line">Data: 2&#x27;s complement, little endian</span><br><span class="line">Version: 1 (current)</span><br><span class="line">OS/ABI: UNIX - System V</span><br><span class="line">ABI Version: 0</span><br><span class="line">Type: DYN (Position-Independent Executable file)</span><br><span class="line">Machine: Advanced Micro Devices X86-64</span><br><span class="line">Version: 0x1</span><br><span class="line">Entry point address: 0x1040</span><br><span class="line">Start of program headers: 64 (bytes into file)</span><br><span class="line">Start of section headers: 13552 (bytes into file)</span><br><span class="line">Flags: 0x0</span><br><span class="line">Size of this header: 64 (bytes)</span><br><span class="line">Size of program headers: 56 (bytes)</span><br><span class="line">Number of program headers: 13</span><br><span class="line">Size of section headers: 64 (bytes)</span><br><span class="line">Number of section headers: 30</span><br><span class="line">Section header string table index: 29</span><br><span class="line"></span><br><span class="line">Section Headers:</span><br><span class="line">[Nr] Name              Type             Address           Offset</span><br><span class="line">    Size              EntSize          Flags  Link  Info  Align</span><br><span class="line">[ 0]                   NULL             0000000000000000  00000000</span><br><span class="line">    0000000000000000  0000000000000000           0     0     0</span><br><span class="line">[ 1] .interp           PROGBITS         0000000000000318  00000318</span><br><span class="line">    000000000000001c  0000000000000000   A       0     0     1</span><br><span class="line">[ 2] .note.gnu.pr[...] NOTE             0000000000000338  00000338</span><br><span class="line">    0000000000000040  0000000000000000   A       0     0     8</span><br><span class="line">[ 3] .note.gnu.bu[...] NOTE             0000000000000378  00000378</span><br><span class="line">    0000000000000024  0000000000000000   A       0     0     4</span><br><span class="line">[ 4] .note.ABI-tag     NOTE             000000000000039c  0000039c</span><br><span class="line">    0000000000000020  0000000000000000   A       0     0     4</span><br><span class="line">[ 5] .gnu.hash         GNU_HASH         00000000000003c0  000003c0</span><br><span class="line">    000000000000001c  0000000000000000   A       6     0     8</span><br><span class="line">[ 6] .dynsym           DYNSYM           00000000000003e0  000003e0</span><br><span class="line">    00000000000000a8  0000000000000018   A       7     1     8</span><br><span class="line">[ 7] .dynstr           STRTAB           0000000000000488  00000488</span><br><span class="line">    000000000000008d  0000000000000000   A       0     0     1</span><br><span class="line">[ 8] .gnu.version      VERSYM           0000000000000516  00000516</span><br><span class="line">    000000000000000e  0000000000000002   A       6     0     2</span><br><span class="line">[ 9] .gnu.version_r    VERNEED          0000000000000528  00000528</span><br><span class="line">    0000000000000030  0000000000000000   A       7     1     8</span><br><span class="line">[10] .rela.dyn         RELA             0000000000000558  00000558</span><br><span class="line">    00000000000000c0  0000000000000018   A       6     0     8</span><br><span class="line">[11] .rela.plt         RELA             0000000000000618  00000618</span><br><span class="line">    0000000000000018  0000000000000018  AI       6    23     8</span><br><span class="line">[12] .init             PROGBITS         0000000000001000  00001000</span><br><span class="line">    000000000000001b  0000000000000000  AX       0     0     4</span><br><span class="line">[13] .plt              PROGBITS         0000000000001020  00001020</span><br><span class="line">    0000000000000020  0000000000000010  AX       0     0     16</span><br><span class="line">[14] .text             PROGBITS         0000000000001040  00001040</span><br><span class="line">    000000000000012f  0000000000000000  AX       0     0     16</span><br><span class="line">[15] .fini             PROGBITS         0000000000001170  00001170</span><br><span class="line">    000000000000000d  0000000000000000  AX       0     0     4</span><br><span class="line">[16] .rodata           PROGBITS         0000000000002000  00002000</span><br><span class="line">    000000000000000f  0000000000000000   A       0     0     4</span><br><span class="line">[17] .eh_frame_hdr     PROGBITS         0000000000002010  00002010</span><br><span class="line">    000000000000002c  0000000000000000   A       0     0     4</span><br><span class="line">[18] .eh_frame         PROGBITS         0000000000002040  00002040</span><br><span class="line">    000000000000009c  0000000000000000   A       0     0     8</span><br><span class="line">[19] .init_array       INIT_ARRAY       0000000000003dd0  00002dd0</span><br><span class="line">    0000000000000008  0000000000000008  WA       0     0     8</span><br><span class="line">[20] .fini_array       FINI_ARRAY       0000000000003dd8  00002dd8</span><br><span class="line">    0000000000000008  0000000000000008  WA       0     0     8</span><br><span class="line">[21] .dynamic          DYNAMIC          0000000000003de0  00002de0</span><br><span class="line">    00000000000001e0  0000000000000010  WA       7     0     8</span><br><span class="line">[22] .got              PROGBITS         0000000000003fc0  00002fc0</span><br><span class="line">    0000000000000028  0000000000000008  WA       0     0     8</span><br><span class="line">[23] .got.plt          PROGBITS         0000000000003fe8  00002fe8</span><br><span class="line">    0000000000000020  0000000000000008  WA       0     0     8</span><br><span class="line">[24] .data             PROGBITS         0000000000004008  00003008</span><br><span class="line">    0000000000000010  0000000000000000  WA       0     0     8</span><br><span class="line">[25] .bss              NOBITS           0000000000004018  00003018</span><br><span class="line">    0000000000000008  0000000000000000  WA       0     0     1</span><br><span class="line">[26] .comment          PROGBITS         0000000000000000  00003018</span><br><span class="line">    0000000000000036  0000000000000001  MS       0     0     1</span><br><span class="line">[27] .symtab           SYMTAB           0000000000000000  00003050</span><br><span class="line">    0000000000000258  0000000000000018          28     6     8</span><br><span class="line">[28] .strtab           STRTAB           0000000000000000  000032a8</span><br><span class="line">    000000000000012d  0000000000000000           0     0     1</span><br><span class="line">[29] .shstrtab         STRTAB           0000000000000000  000033d5</span><br><span class="line">    0000000000000116  0000000000000000           0     0     1</span><br><span class="line">Key to Flags:</span><br><span class="line">W (write), A (alloc), X (execute), M (merge), S (strings), I (info),</span><br><span class="line">L (link order), O (extra OS processing required), G (group), T (TLS),</span><br><span class="line">C (compressed), x (unknown), o (OS specific), E (exclude),</span><br><span class="line">D (mbind), l (large), p (processor specific)</span><br><span class="line"></span><br><span class="line">There are no section groups in this file.</span><br><span class="line"></span><br><span class="line">Program Headers:</span><br><span class="line">Type           Offset             VirtAddr           PhysAddr</span><br><span class="line">                FileSiz            MemSiz              Flags  Align</span><br><span class="line">PHDR           0x0000000000000040 0x0000000000000040 0x0000000000000040</span><br><span class="line">                0x00000000000002d8 0x00000000000002d8  R      0x8</span><br><span class="line">INTERP         0x0000000000000318 0x0000000000000318 0x0000000000000318</span><br><span class="line">                0x000000000000001c 0x000000000000001c  R      0x1</span><br><span class="line">    [Requesting program interpreter: /lib64/ld-linux-x86-64.so.2]</span><br><span class="line">LOAD           0x0000000000000000 0x0000000000000000 0x0000000000000000</span><br><span class="line">                0x0000000000000630 0x0000000000000630  R      0x1000</span><br><span class="line">LOAD           0x0000000000001000 0x0000000000001000 0x0000000000001000</span><br><span class="line">                0x000000000000017d 0x000000000000017d  R E    0x1000</span><br><span class="line">LOAD           0x0000000000002000 0x0000000000002000 0x0000000000002000</span><br><span class="line">                0x00000000000000dc 0x00000000000000dc  R      0x1000</span><br><span class="line">LOAD           0x0000000000002dd0 0x0000000000003dd0 0x0000000000003dd0</span><br><span class="line">                0x0000000000000248 0x0000000000000250  RW     0x1000</span><br><span class="line">DYNAMIC        0x0000000000002de0 0x0000000000003de0 0x0000000000003de0</span><br><span class="line">                0x00000000000001e0 0x00000000000001e0  RW     0x8</span><br><span class="line">NOTE           0x0000000000000338 0x0000000000000338 0x0000000000000338</span><br><span class="line">                0x0000000000000040 0x0000000000000040  R      0x8</span><br><span class="line">NOTE           0x0000000000000378 0x0000000000000378 0x0000000000000378</span><br><span class="line">                0x0000000000000044 0x0000000000000044  R      0x4</span><br><span class="line">GNU_PROPERTY   0x0000000000000338 0x0000000000000338 0x0000000000000338</span><br><span class="line">                0x0000000000000040 0x0000000000000040  R      0x8</span><br><span class="line">GNU_EH_FRAME   0x0000000000002010 0x0000000000002010 0x0000000000002010</span><br><span class="line">                0x000000000000002c 0x000000000000002c  R      0x4</span><br><span class="line">GNU_STACK      0x0000000000000000 0x0000000000000000 0x0000000000000000</span><br><span class="line">                0x0000000000000000 0x0000000000000000  RW     0x10</span><br><span class="line">GNU_RELRO      0x0000000000002dd0 0x0000000000003dd0 0x0000000000003dd0</span><br><span class="line">                0x0000000000000230 0x0000000000000230  R      0x1</span><br><span class="line"></span><br><span class="line">Section to Segment mapping:</span><br><span class="line">Segment Sections...</span><br><span class="line">00</span><br><span class="line">01     .interp</span><br><span class="line">02     .interp .note.gnu.property .note.gnu.build-id .note.ABI-tag .gnu.hash .dynsym .dynstr .gnu.version .gnu.version_r .rela.dyn .rela.plt</span><br><span class="line">03     .init .plt .text .fini</span><br><span class="line">04     .rodata .eh_frame_hdr .eh_frame</span><br><span class="line">05     .init_array .fini_array .dynamic .got .got.plt .data .bss</span><br><span class="line">06     .dynamic</span><br><span class="line">07     .note.gnu.property</span><br><span class="line">08     .note.gnu.build-id .note.ABI-tag</span><br><span class="line">09     .note.gnu.property</span><br><span class="line">10     .eh_frame_hdr</span><br><span class="line">11</span><br><span class="line">12     .init_array .fini_array .dynamic .got</span><br><span class="line"></span><br><span class="line">Dynamic section at offset 0x2de0 contains 26 entries:</span><br><span class="line">Tag        Type                         Name/Value</span><br><span class="line">0x0000000000000001 (NEEDED)             Shared library: [libc.so.6]</span><br><span class="line">0x000000000000000c (INIT)               0x1000</span><br><span class="line">0x000000000000000d (FINI)               0x1170</span><br><span class="line">0x0000000000000019 (INIT_ARRAY)         0x3dd0</span><br><span class="line">0x000000000000001b (INIT_ARRAYSZ)       8 (bytes)</span><br><span class="line">0x000000000000001a (FINI_ARRAY)         0x3dd8</span><br><span class="line">0x000000000000001c (FINI_ARRAYSZ)       8 (bytes)</span><br><span class="line">0x000000006ffffef5 (GNU_HASH)           0x3c0</span><br><span class="line">0x0000000000000005 (STRTAB)             0x488</span><br><span class="line">0x0000000000000006 (SYMTAB)             0x3e0</span><br><span class="line">0x000000000000000a (STRSZ)              141 (bytes)</span><br><span class="line">0x000000000000000b (SYMENT)             24 (bytes)</span><br><span class="line">0x0000000000000015 (DEBUG)              0x0</span><br><span class="line">0x0000000000000003 (PLTGOT)             0x3fe8</span><br><span class="line">0x0000000000000002 (PLTRELSZ)           24 (bytes)</span><br><span class="line">0x0000000000000014 (PLTREL)             RELA</span><br><span class="line">0x0000000000000017 (JMPREL)             0x618</span><br><span class="line">0x0000000000000007 (RELA)               0x558</span><br><span class="line">0x0000000000000008 (RELASZ)             192 (bytes)</span><br><span class="line">0x0000000000000009 (RELAENT)            24 (bytes)</span><br><span class="line">0x000000006ffffffb (FLAGS_1)            Flags: PIE</span><br><span class="line">0x000000006ffffffe (VERNEED)            0x528</span><br><span class="line">0x000000006fffffff (VERNEEDNUM)         1</span><br><span class="line">0x000000006ffffff0 (VERSYM)             0x516</span><br><span class="line">0x000000006ffffff9 (RELACOUNT)          3</span><br><span class="line">0x0000000000000000 (NULL)               0x0</span><br><span class="line"></span><br><span class="line">Relocation section &#x27;.rela.dyn&#x27; at offset 0x558 contains 8 entries:</span><br><span class="line">Offset          Info           Type           Sym. Value    Sym. Name + Addend</span><br><span class="line">000000003dd0  000000000008 R_X86_64_RELATIVE                    1130</span><br><span class="line">000000003dd8  000000000008 R_X86_64_RELATIVE                    10e0</span><br><span class="line">000000004010  000000000008 R_X86_64_RELATIVE                    4010</span><br><span class="line">000000003fc0  000100000006 R_X86_64_GLOB_DAT 0000000000000000 __libc_start_main@GLIBC_2.34 + 0</span><br><span class="line">000000003fc8  000200000006 R_X86_64_GLOB_DAT 0000000000000000 _ITM_deregisterTM[...] + 0</span><br><span class="line">000000003fd0  000400000006 R_X86_64_GLOB_DAT 0000000000000000 __gmon_start__ + 0</span><br><span class="line">000000003fd8  000500000006 R_X86_64_GLOB_DAT 0000000000000000 _ITM_registerTMCl[...] + 0</span><br><span class="line">000000003fe0  000600000006 R_X86_64_GLOB_DAT 0000000000000000 __cxa_finalize@GLIBC_2.2.5 + 0</span><br><span class="line"></span><br><span class="line">Relocation section &#x27;.rela.plt&#x27; at offset 0x618 contains 1 entry:</span><br><span class="line">Offset          Info           Type           Sym. Value    Sym. Name + Addend</span><br><span class="line">000000004000  000300000007 R_X86_64_JUMP_SLO 0000000000000000 puts@GLIBC_2.2.5 + 0</span><br><span class="line">No processor specific unwind information to decode</span><br><span class="line"></span><br><span class="line">Symbol table &#x27;.dynsym&#x27; contains 7 entries:</span><br><span class="line">Num:    Value          Size Type    Bind   Vis      Ndx Name</span><br><span class="line">    0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND</span><br><span class="line">    1: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND _[...]@GLIBC_2.34 (2)</span><br><span class="line">    2: 0000000000000000     0 NOTYPE  WEAK   DEFAULT  UND _ITM_deregisterT[...]</span><br><span class="line">    3: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND puts@GLIBC_2.2.5 (3)</span><br><span class="line">    4: 0000000000000000     0 NOTYPE  WEAK   DEFAULT  UND __gmon_start__</span><br><span class="line">    5: 0000000000000000     0 NOTYPE  WEAK   DEFAULT  UND _ITM_registerTMC[...]</span><br><span class="line">    6: 0000000000000000     0 FUNC    WEAK   DEFAULT  UND [...]@GLIBC_2.2.5 (3)</span><br><span class="line"></span><br><span class="line">Symbol table &#x27;.symtab&#x27; contains 25 entries:</span><br><span class="line">Num:    Value          Size Type    Bind   Vis      Ndx Name</span><br><span class="line">    0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND</span><br><span class="line">    1: 0000000000000000     0 FILE    LOCAL  DEFAULT  ABS hello.c</span><br><span class="line">    2: 0000000000000000     0 FILE    LOCAL  DEFAULT  ABS</span><br><span class="line">    3: 0000000000003de0     0 OBJECT  LOCAL  DEFAULT   21 _DYNAMIC</span><br><span class="line">    4: 0000000000002010     0 NOTYPE  LOCAL  DEFAULT   17 __GNU_EH_FRAME_HDR</span><br><span class="line">    5: 0000000000003fe8     0 OBJECT  LOCAL  DEFAULT   23 _GLOBAL_OFFSET_TABLE_</span><br><span class="line">    6: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND __libc_start_mai[...]</span><br><span class="line">    7: 0000000000000000     0 NOTYPE  WEAK   DEFAULT  UND _ITM_deregisterT[...]</span><br><span class="line">    8: 0000000000004008     0 NOTYPE  WEAK   DEFAULT   24 data_start</span><br><span class="line">    9: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND puts@GLIBC_2.2.5</span><br><span class="line">    10: 0000000000004018     0 NOTYPE  GLOBAL DEFAULT   24 _edata</span><br><span class="line">    11: 0000000000001170     0 FUNC    GLOBAL HIDDEN    15 _fini</span><br><span class="line">    12: 0000000000001139    22 FUNC    GLOBAL DEFAULT   14 hello</span><br><span class="line">    13: 0000000000004008     0 NOTYPE  GLOBAL DEFAULT   24 __data_start</span><br><span class="line">    14: 0000000000000000     0 NOTYPE  WEAK   DEFAULT  UND __gmon_start__</span><br><span class="line">    15: 0000000000004010     0 OBJECT  GLOBAL HIDDEN    24 __dso_handle</span><br><span class="line">    16: 0000000000002000     4 OBJECT  GLOBAL DEFAULT   16 _IO_stdin_used</span><br><span class="line">    17: 0000000000004020     0 NOTYPE  GLOBAL DEFAULT   25 _end</span><br><span class="line">    18: 0000000000001040    38 FUNC    GLOBAL DEFAULT   14 _start</span><br><span class="line">    19: 0000000000004018     0 NOTYPE  GLOBAL DEFAULT   25 __bss_start</span><br><span class="line">    20: 000000000000114f    32 FUNC    GLOBAL DEFAULT   14 main</span><br><span class="line">    21: 0000000000004018     0 OBJECT  GLOBAL HIDDEN    24 __TMC_END__</span><br><span class="line">    22: 0000000000000000     0 NOTYPE  WEAK   DEFAULT  UND _ITM_registerTMC[...]</span><br><span class="line">    23: 0000000000000000     0 FUNC    WEAK   DEFAULT  UND __cxa_finalize@G[...]</span><br><span class="line">    24: 0000000000001000     0 FUNC    GLOBAL HIDDEN    12 _init</span><br><span class="line"></span><br><span class="line">Version symbols section &#x27;.gnu.version&#x27; contains 7 entries:</span><br><span class="line">Addr: 0x0000000000000516  Offset: 0x00000516  Link: 6 (.dynsym)</span><br><span class="line">000:   0 (*local*)       2 (GLIBC_2.34)    1 (*global*)      3 (GLIBC_2.2.5)</span><br><span class="line">004:   1 (*global*)      1 (*global*)      3 (GLIBC_2.2.5)</span><br><span class="line"></span><br><span class="line">Version needs section &#x27;.gnu.version_r&#x27; contains 1 entry:</span><br><span class="line">Addr: 0x0000000000000528  Offset: 0x00000528  Link: 7 (.dynstr)</span><br><span class="line">000000: Version: 1  File: libc.so.6  Cnt: 2</span><br><span class="line">0x0010:   Name: GLIBC_2.2.5  Flags: none  Version: 3</span><br><span class="line">0x0020:   Name: GLIBC_2.34  Flags: none  Version: 2</span><br><span class="line"></span><br><span class="line">Displaying notes found in: .note.gnu.property</span><br><span class="line">Owner                Data size 4Description</span><br><span class="line">GNU                  0x000000304NT_GNU_PROPERTY_TYPE_0</span><br><span class="line">    Properties: x86 ISA needed: x86-64-baseline</span><br><span class="line">    x86 feature used: x86</span><br><span class="line">    x86 ISA used:</span><br><span class="line"></span><br><span class="line">Displaying notes found in: .note.gnu.build-id</span><br><span class="line">Owner                Data size 4Description</span><br><span class="line">GNU                  0x000000144NT_GNU_BUILD_ID (unique build ID bitstring)</span><br><span class="line">    Build ID: 7e623b7c81161e12006d8a813799940eaafd1901</span><br><span class="line"></span><br><span class="line">Displaying notes found in: .note.ABI-tag</span><br><span class="line">Owner                Data size 4Description</span><br><span class="line">GNU                  0x000000104NT_GNU_ABI_TAG (ABI version tag)</span><br><span class="line">    OS: Linux, ABI: 4.4.0</span><br></pre></td></tr></table></figure></div></div></details><ul><li><p>There’re a lot of information shown here. Let’s just focus on what we need: <strong>function symbols</strong>.</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line">Symbol table &#x27;.symtab&#x27; contains 25 entries:</span><br><span class="line">Num:   Value             Size Type    Bind   Vis      Ndx Name</span><br><span class="line">    0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND</span><br><span class="line">    1: 0000000000000000     0 FILE    LOCAL  DEFAULT  ABS hello.c</span><br><span class="line">    2: 0000000000000000     0 FILE    LOCAL  DEFAULT  ABS</span><br><span class="line">    3: 0000000000003de0     0 OBJECT  LOCAL  DEFAULT   21 _DYNAMIC</span><br><span class="line">    4: 0000000000002010     0 NOTYPE  LOCAL  DEFAULT   17 __GNU_EH_FRAME_HDR</span><br><span class="line">    5: 0000000000003fe8     0 OBJECT  LOCAL  DEFAULT   23 _GLOBAL_OFFSET_TABLE_</span><br><span class="line">    6: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND __libc_start_mai[...]</span><br><span class="line">    7: 0000000000000000     0 NOTYPE  WEAK   DEFAULT  UND _ITM_deregisterT[...]</span><br><span class="line">    8: 0000000000004008     0 NOTYPE  WEAK   DEFAULT   24 data_start</span><br><span class="line">    9: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND puts@GLIBC_2.2.5</span><br><span class="line">   10: 0000000000004018     0 NOTYPE  GLOBAL DEFAULT   24 _edata</span><br><span class="line">   11: 0000000000001170     0 FUNC    GLOBAL HIDDEN    15 _fini</span><br><span class="line">   12: 0000000000001139    22 FUNC    GLOBAL DEFAULT   14 hello</span><br><span class="line">   13: 0000000000004008     0 NOTYPE  GLOBAL DEFAULT   24 __data_start</span><br><span class="line">   14: 0000000000000000     0 NOTYPE  WEAK   DEFAULT  UND __gmon_start__</span><br><span class="line">   15: 0000000000004010     0 OBJECT  GLOBAL HIDDEN    24 __dso_handle</span><br><span class="line">   16: 0000000000002000     4 OBJECT  GLOBAL DEFAULT   16 _IO_stdin_used</span><br><span class="line">   17: 0000000000004020     0 NOTYPE  GLOBAL DEFAULT   25 _end</span><br><span class="line">   18: 0000000000001040    38 FUNC    GLOBAL DEFAULT   14 _start</span><br><span class="line">   19: 0000000000004018     0 NOTYPE  GLOBAL DEFAULT   25 __bss_start</span><br><span class="line">   20: 000000000000114f    32 FUNC    GLOBAL DEFAULT   14 main</span><br><span class="line">   21: 0000000000004018     0 OBJECT  GLOBAL HIDDEN    24 __TMC_END__</span><br><span class="line">   22: 0000000000000000     0 NOTYPE  WEAK   DEFAULT  UND _ITM_registerTMC[...]</span><br><span class="line">   23: 0000000000000000     0 FUNC    WEAK   DEFAULT  UND __cxa_finalize@G[...]</span><br><span class="line">   24: 0000000000001000     0 FUNC    GLOBAL HIDDEN    12 _init</span><br></pre></td></tr></table></figure></div></li><li><p>From the above <em>symbol table</em> we can see that, among all of the symbols, those with type <code>FUNC</code> should be the ones that we want for ftrace.</p></li><li><p>However, it’s not that simple.</p><ul><li>It’s not easy to locate and read the <em>symbol table</em> from an ELF file since it’s in binary.</li><li>The <code>NAME</code> in the <em>symbol table</em> is actually an <strong>offset</strong> used to search from <em>string table</em>.</li></ul></li><li><p>To resolve these two issues, we will need to take a look at the structure of a common ELF file.</p></li></ul><h3 id="ELF-File-Structure"><a href="#ELF-File-Structure" class="headerlink" title="ELF File Structure"></a>ELF File Structure</h3><ul><li><p>Basically, a common ELF file contains an <strong>ELF header</strong>, which includes information about itself, such as:</p><ul><li><code>e_ident</code>: a magic number identifying the file as an ELF file</li><li><code>e_shoff</code>: section header’s offset</li><li><code>e_shnum</code>: count of section headers</li><li><code>e_shstrndx</code>: index of the names’ section in the table</li></ul></li><li><p>Those variables are the guides for us to extract useful content from the ELF file.</p></li><li><p>Other parts of ELF file, such as <em>section header table</em>, <em>string table</em> and <em>section name string table</em>, can be found using these offsets extracted from <strong>ELF header</strong>.</p></li><li><p>But how exactly should we read them from an ELF file? The answer is, with the help of <code>&lt;elf.h&gt;</code>.</p></li></ul><h3 id=""><a href="#" class="headerlink" title="&lt;elf.h&gt;"></a><code>&lt;elf.h&gt;</code></h3><p><code>man 5 elf</code> :</p><blockquote><p>The header file &lt;elf.h&gt; defines the format of ELF executable binary files. Amongst these files are normal executable files, relocatable object files, core files, and shared objects.</p></blockquote><blockquote><p>An executable file using the ELF file format cosists of an ELF header, followed by a program header table or a section header table, or both. The ELF header is always at offset zero of the file. The program header table and the section header table’s offset in the file are defined in the ELF header. The two tables describe the rest of the particularities of the file.</p></blockquote><ul><li><p>This header file defines a lot of types(structs) to help users read from the binary ELF files.</p></li><li><p>For example, a struct <code>Elf32_Ehdr</code> of ELF header:</p><div class="code-container" data-rel="C"><figure class="iseeu highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">typedef</span> <span class="class"><span class="keyword">struct</span> &#123;</span></span><br><span class="line">    <span class="type">unsigned</span> <span class="type">char</span>  e_ident[EI_NIDENT];  <span class="comment">/* Magic number and other info */</span></span><br><span class="line">    Elf32_Half  e_type;         <span class="comment">/* Object file type */</span></span><br><span class="line">    Elf32_Half  e_machine;      <span class="comment">/* Architecture */</span></span><br><span class="line">    Elf32_Word  e_version;      <span class="comment">/* Object file version */</span></span><br><span class="line">    Elf32_Addr  e_entry;        <span class="comment">/* Entry point virtual address */</span></span><br><span class="line">    Elf32_Off   e_phoff;        <span class="comment">/* Program header table file offset */</span></span><br><span class="line">    Elf32_Off   e_shoff;        <span class="comment">/* Section header table file offset */</span></span><br><span class="line">    Elf32_Word  e_flags;        <span class="comment">/* Processor-specific flags */</span></span><br><span class="line">    Elf32_Half  e_ehsize;       <span class="comment">/* ELF header size in bytes */</span></span><br><span class="line">    Elf32_Half  e_phentsize;    <span class="comment">/* Program header table entry size */</span></span><br><span class="line">    Elf32_Half  e_phnum;        <span class="comment">/* Program header table entry count */</span></span><br><span class="line">    Elf32_Half  e_shentsize;    <span class="comment">/* Section header table entry size */</span></span><br><span class="line">    Elf32_Half  e_shnum;        <span class="comment">/* Section header table entry count */</span></span><br><span class="line">    Elf32_Half  e_shstrndx;4    <span class="comment">/* Section header string table index */</span></span><br><span class="line">&#125; Elf32_Ehdr;</span><br></pre></td></tr></table></figure></div></li><li><p>We can use it to define a variable and store the content read from the file to it:</p><div class="code-container" data-rel="C"><figure class="iseeu highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// read the whole ELF file</span></span><br><span class="line">ElfW(Ehdr) ELF_header;</span><br><span class="line">fread(&amp;ELF_header, <span class="keyword">sizeof</span>(ELF_header), <span class="number">1</span>, file);</span><br></pre></td></tr></table></figure></div><ul><li><p>BTW, there’s a macro <code>ElfW</code> used here:</p><div class="code-container" data-rel="C"><figure class="iseeu highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">if</span> defined(CONFIG_RV64)</span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> ElfW(type) Elf64_##type</span></span><br><span class="line"><span class="meta">#<span class="keyword">else</span></span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> ElfW(type) Elf32_##type</span></span><br><span class="line"><span class="meta">#<span class="keyword">endif</span> <span class="comment">/* if defined (CONFIG_RV64) */</span></span></span><br></pre></td></tr></table></figure></div></li><li><p>I have to say, that, macro is kinda useful. While I disliked it when first encountered it.</p></li></ul></li><li><p>We could then use the <code>e_shoff</code> from the ELF header to locate the position of section headers:</p><div class="code-container" data-rel="C"><figure class="iseeu highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// read section headers</span></span><br><span class="line"><span class="type">size_t</span> sh_entry_size = <span class="keyword">sizeof</span>(ElfW(Shdr));</span><br><span class="line">ElfW(Shdr) *shdrs = <span class="built_in">malloc</span>(ELF_header.e_shnum * sh_entry_size);</span><br><span class="line">Assert(shdrs, ANSI_FMT(<span class="string">&quot;Failed to allocate memory for section headers\n&quot;</span>,</span><br><span class="line">                       ANSI_FG_RED));</span><br><span class="line"></span><br><span class="line">fseek(file, ELF_header.e_shoff, SEEK_SET);</span><br><span class="line">fread(shdrs, sh_entry_size, ELF_header.e_shnum, file);</span><br></pre></td></tr></table></figure></div></li><li><p>And so on.</p></li></ul><h2 id="Missing-Symbols-in-ELF"><a href="#Missing-Symbols-in-ELF" class="headerlink" title="Missing Symbols in ELF"></a>Missing Symbols in ELF</h2><ul><li><p>Sometimes <code>gcc</code> automatically inlines functions written in the source file.</p></li><li><p>In this case, they are not shown in the symbol table as there’s no such symbol after pre-compilation.</p></li><li><p>We can use an attribute to stop <code>gcc</code> from inlining these functions:</p><div class="code-container" data-rel="C"><figure class="iseeu highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">int</span> <span class="title function_">is_prime</span><span class="params">(<span class="type">int</span> n)</span> __<span class="title function_">attribute__</span><span class="params">((noinline))</span>;</span><br></pre></td></tr></table></figure></div></li></ul><h2 id="use-after-free-Error"><a href="#use-after-free-Error" class="headerlink" title="use-after-free Error"></a><em>use-after-free</em> Error</h2><div class="code-container" data-rel="C"><figure class="iseeu highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">FuncSym func = &#123;</span><br><span class="line">    .name = string_table[symbol_table[i].st_name],</span><br><span class="line">    .value = symbol_table[i].st_value,</span><br><span class="line">    .size = symbol_table[i].st_size,</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line">...</span><br><span class="line"></span><br><span class="line"><span class="built_in">free</span>(string_table);</span><br></pre></td></tr></table></figure></div><ul><li><p>I ran into this problem as the code shown above. This is the first time I encounter with a use-after-free error, so it took me a while to figure out what’s happening.</p></li><li><p>The tricky part of this bug is that, there’re no errors reported by compiler or the program itself, it just caused the output info from the function table to be random characters.</p></li><li><p>Solution: use <code>strdup</code> to duplicate a string from the <code>string_table</code>.</p><div class="code-container" data-rel="C"><figure class="iseeu highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">FuncSym func = &#123;</span><br><span class="line">    .name = strdup(&amp;string_table[symbol_table[i].st_name]),</span><br><span class="line">    .value = symbol_table[i].st_value,</span><br><span class="line">    .size = symbol_table[i].st_size,</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure></div></li></ul><h2 id="Implementing"><a href="#Implementing" class="headerlink" title="Implementing &lt;string.h&gt;"></a>Implementing <code>&lt;string.h&gt;</code></h2><ul><li>Recording some interesting implementation in <code>&lt;string.h&gt;</code> here.</li></ul><h3 id="strcmp"><a href="#strcmp" class="headerlink" title="strcmp"></a><code>strcmp</code></h3><div class="code-container" data-rel="C"><figure class="iseeu highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">int</span> <span class="title function_">strcmp</span><span class="params">(<span class="type">const</span> <span class="type">char</span> *s1, <span class="type">const</span> <span class="type">char</span> *s2)</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (s1 == <span class="literal">NULL</span> || s2 == <span class="literal">NULL</span>)</span><br><span class="line">        <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">while</span> (*s1 != <span class="string">&#x27;\0&#x27;</span> &amp;&amp; (*s1 == *s2)) &#123;</span><br><span class="line">        s1++;</span><br><span class="line">        s2++;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> *(<span class="type">unsigned</span> <span class="type">char</span> *)s1 - *(<span class="type">unsigned</span> <span class="type">char</span> *)s2;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div><ul><li>Iterating through the two strings and returns the difference between the first unmatched character.</li></ul><h3 id="memset"><a href="#memset" class="headerlink" title="memset"></a><code>memset</code></h3><div class="code-container" data-rel="C"><figure class="iseeu highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">void</span> *<span class="title function_">memset</span><span class="params">(<span class="type">void</span> *s, <span class="type">int</span> c, <span class="type">size_t</span> n)</span> &#123;</span><br><span class="line">    <span class="type">unsigned</span> <span class="type">char</span> *p = s;</span><br><span class="line">    <span class="keyword">for</span> (<span class="type">size_t</span> i = <span class="number">0</span>; i &lt; n; i++) &#123;</span><br><span class="line">        *p = (<span class="type">unsigned</span> <span class="type">char</span>)c;</span><br><span class="line">        p++;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> s;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></div><ul><li>Use <code>unsigned char</code> as a <em>byte</em> in memory.</li></ul><h2 id="Implementing-sprintf"><a href="#Implementing-sprintf" class="headerlink" title="Implementing sprintf"></a>Implementing <code>sprintf</code></h2><ul><li>For the moment we would only need to implement two specifiers: <code>%d</code> and <code>%s</code>.</li></ul><hr><ul><li><p>I was first very hesitate to begin with this implementation cuz I thought it was going to be difficult. But as soon as I dived into it, it’s actually not that hard as I anticipated.</p></li><li><p>My first idea was to maintain a dynamic array with <code>malloc</code> and <code>realloc</code> to store the final buffer for output. But I soon found that I would have to implement <code>malloc</code> and <code>realloc</code> as well this way…So I turned to a fiexed length buffer instead.</p></li></ul><hr><ul><li>The idea is simple:<ul><li>Read the <code>fmt</code> argument character by character, put that character into buffer if it is not <code>%</code>, which is the start of a specifier.</li><li>When encountering a specifier, use <code>va_arg</code> to pop the next arguement with proper type, convert it into a character or a string, and then take it into the buffer.</li><li>When the parsing of <code>fmt</code> is done, use <code>strcpy</code> we’ve implemented before to copy the buffer into <code>out</code>.</li></ul></li></ul><h2 id="DiffTest"><a href="#DiffTest" class="headerlink" title="DiffTest"></a>DiffTest</h2><h3 id="device-tree-compiler-on-ArchLinux"><a href="#device-tree-compiler-on-ArchLinux" class="headerlink" title="device-tree-compiler on ArchLinux"></a><code>device-tree-compiler</code> on ArchLinux</h3><ul><li><p><code>device-tree-compiler</code> is a package required for conducting difftest on <code>spike</code>.</p></li><li><p>This pacakge is named <code>dtc</code> in Arch Linux official repo.</p><div class="code-container" data-rel="Bash"><figure class="iseeu highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">sudo</span> pacman -Sy dtc</span><br></pre></td></tr></table></figure></div></li></ul><h2 id="How-Does-the-VM-Intercepts-Memory-Reading-Action"><a href="#How-Does-the-VM-Intercepts-Memory-Reading-Action" class="headerlink" title="How Does the VM Intercepts Memory Reading Action?"></a>How Does the VM Intercepts Memory Reading Action?</h2><ul><li><p>Why could we directly read memory-mapped address in a way of pointer dereferencing in AM?</p><div class="code-container" data-rel="C"><figure class="iseeu highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">static</span> <span class="keyword">inline</span> <span class="type">uint8_t</span> <span class="title function_">inb</span><span class="params">(<span class="type">uintptr_t</span> addr)</span> &#123; <span class="keyword">return</span> *(<span class="keyword">volatile</span> <span class="type">uint8_t</span> *)addr; &#125;</span><br></pre></td></tr></table></figure></div></li><li><p>Cuz these memory reading codes will be compiled to assembly, which is basically riscv32 instructions containing the address to be read. When nemu proceeds through these instructions, it will parse it and invoke <code>paddr_read</code> function(<code>Mr</code> macro, which invoke <code>vaddr_read</code> -&gt; <code>paddr_read</code>), and that’s where the callback function binds to a mapped memory area reading is invoked.</p></li></ul><h3 id="Why-volatile"><a href="#Why-volatile" class="headerlink" title="Why volatile ?"></a>Why <code>volatile</code> ?</h3><ul><li>In short, <code>volatile</code> is used to prevent the compiler from optimizing away memory access, especially in this case, which is dealing with memory-mapped IO (MMIO).</li><li>Without <code>volatile</code>, the compiler might optimize away repeated memory reads or writes, assuming the memory value won’t change unexpectedly. This assumption is valid for normal variables, <strong>but memory-mapped registers and hardware I&#x2F;O can change asynchronously.</strong></li><li>For example, reading from an MMIO address might trigger a hardware event or return different values based on the state of the peripheral device. Without <code>volatile</code>, the compiler might cache the value read from memory in a register and never actually perform the memory read again.</li></ul><h2 id="Link-to-Part2"><a href="#Link-to-Part2" class="headerlink" title="Link to Part2"></a><a href="https://blog.imlast.top/2024/11/22/nju-pa-2-part2">Link to Part2</a></h2>]]>
    </content>
    <id>https://blog.imlast.top/2024/10/02/nju-pa-2/</id>
    <link href="https://blog.imlast.top/2024/10/02/nju-pa-2/"/>
    <published>2024-10-02T17:37:00.000Z</published>
    <summary>Implementing specific details of the RISC-V instruction set</summary>
    <title>
      <![CDATA[PA2 Part 1 - Instruction Set Implementation & KLIB]]>
    </title>
    <updated>2024-10-02T17:37:00.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>Last</name>
    </author>
    <category term="笔记" scheme="https://blog.imlast.top/categories/%E7%AC%94%E8%AE%B0/"/>
    <category term="make" scheme="https://blog.imlast.top/tags/make/"/>
    <category term="pa" scheme="https://blog.imlast.top/tags/pa/"/>
    <content>
      <![CDATA[<div class="callout callout--titled warning mb-4 rounded-small shadow-redefine-flat bg-(--callout-bg-color) p-3 pl-1 relative flex flex-row gap-2"><div role="none" class="rounded-full self-stretch w-0.5 bg-(--callout-primary-color) shrink-0 opacity-60"></div><div class="flex flex-col gap-2"><div class="callout__title flex items-center gap-2 font-semibold tracking-tight"><i class="callout__icon fa-triangle-exclamation leading-none text-(--callout-primary-color) text-sm shrink-0"></i> Warning</div><div class="callout__content markdown-body flex-1 min-w-0"><p>If someone is reading this blog, please be aware that the writer <strong>did not</strong> consider the experience of the other readers.<br>After all, the most important part is about writing things down for better memorization.</p></div></div></div><h2 id="Preface"><a href="#Preface" class="headerlink" title="Preface"></a>Preface</h2><ul><li>Due to my laziness in PA1, I haven’t really read any Makefile in the ics project, which has caused many troubles in my following study.</li><li>To me, Makefile is even horrible than the C programming language as it is literally completely new to me. (unlike C, at least I had learnt it in my college lessons)</li><li>I believe that if I continue ignoring these Makefiles and indulging my fear, there will be more troubles waiting in the future.</li><li>So I decided to take a thorough look at the the Makefiles in nemu.</li></ul><hr><ul><li><p>BTW, PA provides a way of reading Makefiles in a ‘relatively concise’ way, which is rendering Makefiles as html:</p><div class="code-container" data-rel="Makefile"><figure class="iseeu highlight makefile"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">### *Get a more readable version of this Makefile* by `make html` (requires python-markdown)</span></span><br><span class="line"><span class="section">html:</span></span><br><span class="line">    cat Makefile | sed &#x27;s/^\([^<span class="comment">#]\)/    \1/g&#x27; | markdown_py &gt; Makefile.html</span></span><br></pre></td></tr></table></figure></div></li><li><p>The result looks like this:</p><p><img src="https://s2.loli.net/2024/09/29/eOEZzwgL5kuofS1.png" alt="image.png"></p></li><li><p>Honestly, this seems worse as the browser does not provide syntax highlight for Makefile…</p></li></ul><h2 id="make-run"><a href="#make-run" class="headerlink" title="make run"></a><code>make run</code></h2><ul><li>Let’s start simple: take a look at what is written inside in the Makefile under nemu root diretory.</li></ul><h3 id="Sanity-Check"><a href="#Sanity-Check" class="headerlink" title="Sanity Check"></a>Sanity Check</h3><div class="code-container" data-rel="Makefile"><figure class="iseeu highlight makefile"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># Sanity check</span></span><br><span class="line"><span class="keyword">ifeq</span> (<span class="variable">$(<span class="built_in">wildcard</span> <span class="variable">$(NEMU_HOME)</span>/src/nemu-main.c)</span>,)</span><br><span class="line">  <span class="variable">$(<span class="built_in">error</span> NEMU_HOME=<span class="variable">$(NEMU_HOME)</span> is not a NEMU repo)</span></span><br><span class="line"><span class="keyword">endif</span></span><br></pre></td></tr></table></figure></div><ul><li>Generally speaking, <code>make</code> checks whether the directory defined in environment variable <code>NEMU_HOME</code> contains <code>src/nemu-main.c</code>.</li><li>If not, throw an error message and quit the build process.</li></ul><hr><h4 id="ifeq-endif"><a href="#ifeq-endif" class="headerlink" title="ifeq...endif"></a><code>ifeq...endif</code></h4><ul><li><p>Pretty simple.</p></li><li><p>example:</p><div class="code-container" data-rel="Makefile"><figure class="iseeu highlight makefile"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">ifeq</span> (<span class="string">&quot;hello&quot;</span>, <span class="string">&quot;hello world&quot;</span>)</span><br><span class="line">    @echo <span class="string">&quot;EQUAL&quot;</span></span><br><span class="line"><span class="keyword">else</span></span><br><span class="line">    @echo <span class="string">&quot;NOT EQUAL&quot;</span></span><br><span class="line"><span class="keyword">endif</span></span><br></pre></td></tr></table></figure></div><ul><li>Will echo: <code>NOT EQUAL</code></li></ul></li></ul><h4 id="wildcard"><a href="#wildcard" class="headerlink" title="wildcard"></a><code>wildcard</code></h4><p><a class="link"   href="https://www.gnu.org/software/make/manual/html_node/Wildcard-Function.html" >GNU make manual<i class="fa-solid fa-arrow-up-right ml-[0.2em] font-light align-text-top text-[0.7em] link-icon"></i></a>:</p><blockquote><p>… But wildcard expansion does not normally take place when a variable is set, or inside the arguments of a function. If you want to do wildcard expansion in such places, you need to use the wildcard function.</p></blockquote><ul><li><p>Basically, this function is used to expand wildcard symbols.</p></li><li><p>example:</p><div class="code-container" data-rel="Makefile"><figure class="iseeu highlight makefile"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="section">wildcard:</span></span><br><span class="line">    @echo <span class="variable">$(<span class="built_in">wildcard</span> *.c)</span></span><br></pre></td></tr></table></figure></div><ul><li>Will echo the names of all files under current directly with an extension <code>.c</code>.</li></ul></li></ul><h3 id="error"><a href="#error" class="headerlink" title="error"></a><code>error</code></h3><ul><li>Pretty simple.</li><li>example: <em>No need</em></li></ul><h3 id="Include-variables-and-rules-generated-by-menuconfig"><a href="#Include-variables-and-rules-generated-by-menuconfig" class="headerlink" title="Include variables and rules generated by menuconfig"></a>Include variables and rules generated by <em>menuconfig</em></h3><div class="code-container" data-rel="Makefile"><figure class="iseeu highlight makefile"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">-include</span> <span class="variable">$(NEMU_HOME)</span>/<span class="keyword">include</span>/config/auto.conf</span><br><span class="line"><span class="keyword">-include</span> <span class="variable">$(NEMU_HOME)</span>/<span class="keyword">include</span>/config/auto.conf.cmd</span><br></pre></td></tr></table></figure></div><ul><li>Grasp config variables from other files.</li></ul><h4 id="include"><a href="#include" class="headerlink" title="include"></a><code>include</code></h4><p><a class="link"   href="https://www.gnu.org/software/make/manual/html_node/Include.html" >GNU make manual<i class="fa-solid fa-arrow-up-right ml-[0.2em] font-light align-text-top text-[0.7em] link-icon"></i></a>:</p><blockquote><p>When <code>make</code> processes an include directive, it suspends reading of the containing makefile and reads from each listed file in turn. When that is finished, <code>make</code> resumes reading the makefile in which the directive appears.</p></blockquote><ul><li>Pretty simple.</li><li>example: <em>No need</em></li></ul><hr><ul><li>NB:</li></ul><blockquote><p>If the specified name does not start with a slash (or a drive letter and colon when GNU Make is compiled with MS-DOS &#x2F; MS-Windows path support), and the file is not found in the current directory, several other directories are searched.</p></blockquote><blockquote><p>First, any directories you have specified with the ‘-I’ or ‘–include-dir’ options are searched (see <a class="link"   href="https://www.gnu.org/software/make/manual/html_node/Options-Summary.html" >Summary of Options<i class="fa-solid fa-arrow-up-right ml-[0.2em] font-light align-text-top text-[0.7em] link-icon"></i></a>). Then the following directories (if they exist) are searched, in this order: <code>prefix/include</code> (normally <code>/usr/local/include</code> 1) <code>/usr/gnu/include,</code> <code>/usr/local/include,</code> <code>/usr/include</code>.</p></blockquote><blockquote><p>The <code>.INCLUDE_DIRS</code> variable will contain the current list of directories that make will search for included files. See <a class="link"   href="https://www.gnu.org/software/make/manual/html_node/Special-Variables.html" >Other Special Variables<i class="fa-solid fa-arrow-up-right ml-[0.2em] font-light align-text-top text-[0.7em] link-icon"></i></a>.</p></blockquote><ul><li><p>About <code>-include</code> :</p><blockquote><p>If you want make to simply ignore a makefile which does not exist or cannot be remade, with no error message, use the -include directive instead of include, like this:</p></blockquote><div class="code-container" data-rel="Makefile"><figure class="iseeu highlight makefile"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">-include</span> &lt;filenames&gt;</span><br></pre></td></tr></table></figure></div></li></ul><h3 id="Function-Definition"><a href="#Function-Definition" class="headerlink" title="Function Definition"></a>Function Definition</h3><div class="code-container" data-rel="Makefile"><figure class="iseeu highlight makefile"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">remove_quote = <span class="variable">$(<span class="built_in">patsubst</span> <span class="string">&quot;%&quot;</span>,%,$(1)</span>)</span><br><span class="line"></span><br><span class="line"><span class="comment"># Extract variabls from menuconfig</span></span><br><span class="line">GUEST_ISA ?= <span class="variable">$(<span class="built_in">call</span> remove_quote,<span class="variable">$(CONFIG_ISA)</span>)</span></span><br><span class="line">ENGINE ?= <span class="variable">$(<span class="built_in">call</span> remove_quote,<span class="variable">$(CONFIG_ENGINE)</span>)</span></span><br><span class="line">NAME    = <span class="variable">$(GUEST_ISA)</span>-nemu-<span class="variable">$(ENGINE)</span></span><br></pre></td></tr></table></figure></div><div class="callout callout--titled info mb-4 rounded-small shadow-redefine-flat bg-(--callout-bg-color) p-3 pl-1 relative flex flex-row gap-2"><div role="none" class="rounded-full self-stretch w-0.5 bg-(--callout-primary-color) shrink-0 opacity-60"></div><div class="flex flex-col gap-2"><div class="callout__title flex items-center gap-2 font-semibold tracking-tight"><i class="callout__icon Setting leading-none text-(--callout-primary-color) text-sm shrink-0"></i> Variables</div><div class="callout__content markdown-body flex-1 min-w-0"><p><em>About <code>?=</code>, see <a href="https://blog.imlast.top/2024/09/29/makefile-in-pa/#Setting-Variables">this chapter below</a></em></p></div></div></div><ul><li>Definition and usage of function <code>remove_quote</code>.</li></ul><h4 id="Function-Calls"><a href="#Function-Calls" class="headerlink" title="Function Calls"></a>Function Calls</h4><p><a class="link"   href="https://www.gnu.org/software/make/manual/html_node/Call-Function.html" >GNU make manual<i class="fa-solid fa-arrow-up-right ml-[0.2em] font-light align-text-top text-[0.7em] link-icon"></i></a>:</p><blockquote><p>The call function is unique in that it can be used to create new parameterized functions. You can write a complex expression as the value of a variable, then use call to expand it with different values.</p></blockquote><ul><li><p>Users can define a custom function and store it in a variable, then call it with the <code>call</code> function.</p></li><li><p>example:</p><div class="code-container" data-rel="Makefile"><figure class="iseeu highlight makefile"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">custom_concat = $(1)$(2)</span><br><span class="line"><span class="section">func:</span></span><br><span class="line">    @echo <span class="variable">$(<span class="built_in">call</span> custom_concat,hello,world)</span></span><br></pre></td></tr></table></figure></div><ul><li>Will echo: <code>helloworld</code></li></ul></li></ul><h4 id="Text-Functions"><a href="#Text-Functions" class="headerlink" title="Text Functions"></a>Text Functions</h4><p><a class="link"   href="https://www.gnu.org/software/make/manual/html_node/Text-Functions.html" >GNU make manual<i class="fa-solid fa-arrow-up-right ml-[0.2em] font-light align-text-top text-[0.7em] link-icon"></i></a></p><ul><li><p><code>$(subst from,to,text)</code> :</p><blockquote><p>Performs a textual replacement on the text text: each occurrence of from is replaced by to. The result is substituted for the function call.</p></blockquote><ul><li><p>example:</p><div class="code-container" data-rel="Makefile"><figure class="iseeu highlight makefile"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="variable">$(<span class="built_in">subst</span> ee,EE,feet on the street)</span></span><br></pre></td></tr></table></figure></div></li><li><p>returns: <code>fEEt on the strEEt</code></p></li></ul></li><li><p><code>$(patsubst pattern,replacement,text)</code> :</p><blockquote><p>Finds whitespace-separated words in text that match pattern and replaces them with replacement.</p></blockquote><ul><li><p>example:</p><div class="code-container" data-rel="Makefile"><figure class="iseeu highlight makefile"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="variable">$(<span class="built_in">patsubst</span> %.c,%.o,x.c.c bar.c)</span></span><br></pre></td></tr></table></figure></div></li><li><p>returns: <code>x.c.o bar.o</code></p></li></ul></li></ul><h2 id="Setting-Variables"><a href="#Setting-Variables" class="headerlink" title="Setting Variables"></a>Setting Variables</h2><p><a class="link"   href="https://www.gnu.org/software/make/manual/html_node/Setting.html" >GNU make manual<i class="fa-solid fa-arrow-up-right ml-[0.2em] font-light align-text-top text-[0.7em] link-icon"></i></a></p><hr><blockquote><p>To set a variable from the makefile, write a line starting with the variable name followed by one of the assignment operators <code>=</code>, <code>:=</code>, <code>::=</code>, or <code>:::=</code>. Whatever follows the operator and any initial whitespace on the line becomes the value.</p></blockquote><blockquote><p>Variables defined with <code>=</code> are <strong><em>recursively expanded variables</em></strong>. Variables defined with <code>:=</code> or <code>::=</code> are <strong><em>simply expanded variables</em></strong>; these definitions can contain variable references which will be expanded before the definition is made. Variables defined with <code>:::=</code> are <strong><em>immediately expanded variables</em></strong>. The different assignment operators are described in See <a class="link"   href="https://www.gnu.org/software/make/manual/html_node/Flavors.html" >The Two Flavors of Variables<i class="fa-solid fa-arrow-up-right ml-[0.2em] font-light align-text-top text-[0.7em] link-icon"></i></a>.</p></blockquote><h3 id="Recursively-Expanded-Variable-Assignment"><a href="#Recursively-Expanded-Variable-Assignment" class="headerlink" title="Recursively Expanded Variable Assignment"></a>Recursively Expanded Variable Assignment</h3><ul><li><p>example:</p><div class="code-container" data-rel="Makefile"><figure class="iseeu highlight makefile"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">objects = main.o foo.o bar.o utils.o</span><br></pre></td></tr></table></figure></div></li><li><p>The example above is a simplest recursively expaneded variable <code>objects</code>. Nothing noticable.</p></li></ul><hr><ul><li><p>example:</p><div class="code-container" data-rel="Makefile"><figure class="iseeu highlight makefile"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">foo = <span class="variable">$(bar)</span></span><br><span class="line">bar = <span class="variable">$(ugh)</span></span><br><span class="line">ugh = Huh?</span><br><span class="line"></span><br><span class="line"><span class="section">all:;echo <span class="variable">$(foo)</span></span></span><br></pre></td></tr></table></figure></div></li><li><p>Assigning a variable through another variable.</p></li><li><p>Notice that, the variable called to assign another variable will be expanded whenever it’s value is assigned(substituted): <strong><code>bar</code> is not assigned when we assign it’s value to <code>foo</code>. The value of <code>foo</code> is assigned with <code>Huh?</code> when it is expanded to <code>$(bar)</code> which is assigned when it expanded to <code>$(ugh)</code>.</strong></p></li><li><p>This recursive expansion happens at the time of <strong>use</strong>.</p></li></ul><hr><ul><li><p>This way of assigning variables does not support self-appending, as it would cause an infinite loop in the variable expansion.</p></li><li><p>negative case:</p><div class="code-container" data-rel="Makefile"><figure class="iseeu highlight makefile"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># would cause infinite loop</span></span><br><span class="line">CFLAGS = <span class="variable">$(CFLAGS)</span> -O</span><br></pre></td></tr></table></figure></div></li></ul><h3 id="Simply-Expanded-Variable-Assignment"><a href="#Simply-Expanded-Variable-Assignment" class="headerlink" title="Simply Expanded Variable Assignment"></a>Simply Expanded Variable Assignment</h3><blockquote><p>The value of a simply expanded variable is scanned once, expanding any references to other variables and functions, when the variable is defined. Once that expansion is complete the value of the variable is never expanded again: when the variable is used the value is copied verbatim as the expansion. If the value contained variable references the result of the expansion will contain their values as of the time this variable was defined.</p></blockquote><ul><li><p>This way of assigning variables is the most common is it works like variables in most programming languages, which means that it is more predictable.</p></li><li><p>example:</p><div class="code-container" data-rel="Makefile"><figure class="iseeu highlight makefile"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">x := foo</span><br><span class="line">y := <span class="variable">$(x)</span> bar</span><br><span class="line">x := later</span><br></pre></td></tr></table></figure></div><ul><li>After the assignment, <code>x</code> is <code>later</code>, <code>y</code> is <code>foo bar</code></li></ul></li></ul><hr><ul><li>Note that <code>:=</code> and <code>::=</code> are equivalent.</li></ul><h3 id="Immediately-Expanded-Variable-Assignment"><a href="#Immediately-Expanded-Variable-Assignment" class="headerlink" title="Immediately Expanded Variable Assignment"></a>Immediately Expanded Variable Assignment</h3><blockquote><p>Another form of assignment allows for immediate expansion, but unlike simple assignment the resulting variable is recursive: <strong><em>it will be re-expanded again on every use.</em></strong> In order to avoid unexpected results, after the value is immediately expanded it will automatically be quoted: all instances of <code>$</code> in the value after expansion will be converted into <code>$$.</code> This type of assignment uses the <code>:::=</code> operator.</p></blockquote><div class="callout callout--titled info mb-4 rounded-small shadow-redefine-flat bg-(--callout-bg-color) p-3 pl-1 relative flex flex-row gap-2"><div role="none" class="rounded-full self-stretch w-0.5 bg-(--callout-primary-color) shrink-0 opacity-60"></div><div class="flex flex-col gap-2"><div class="callout__title flex items-center gap-2 font-semibold tracking-tight"><i class="callout__icon fa-code leading-none text-(--callout-primary-color) text-sm shrink-0"></i> TODO:</div><div class="callout__content markdown-body flex-1 min-w-0"><p>This is a relatively new way of assigning variables. Actually, I don’t understand the explanation above(which is from the official document of GNU make), and I didn’t see any usage of such in the ics project.<br>:<br>As a result, I am leaving this as a <em>TODO</em> for future.</p></div></div></div><h3 id="Conditional-Assignment"><a href="#Conditional-Assignment" class="headerlink" title="Conditional Assignment"></a>Conditional Assignment</h3><blockquote><p>There is another assignment operator for variables, <code>?=</code>. This is called a conditional variable assignment operator, because it only has an effect if the variable is not yet defined.</p></blockquote><ul><li><p>example:</p><div class="code-container" data-rel="Makefile"><figure class="iseeu highlight makefile"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">FOO ?= bar</span><br></pre></td></tr></table></figure></div></li><li><p>is equivalent to:</p><div class="code-container" data-rel="Makefile"><figure class="iseeu highlight makefile"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">ifeq</span> (<span class="variable">$(<span class="built_in">origin</span> FOO)</span>, undefined)</span><br><span class="line">    FOO = bar</span><br><span class="line"><span class="keyword">endif</span></span><br></pre></td></tr></table></figure></div></li><li><p>Note that a variable set to an empty value is still defined, so <code>?=</code> will not set that variable.</p></li></ul><h3 id="Shell-Assignment"><a href="#Shell-Assignment" class="headerlink" title="Shell Assignment"></a>Shell Assignment</h3><blockquote><p>The shell assignment operator ‘!&#x3D;’ can be used to execute a shell script and set a variable to its output. This operator first evaluates the right-hand side, then passes that result to the shell for execution.</p></blockquote><ul><li><p>example:</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">FILES != ls</span><br></pre></td></tr></table></figure></div></li><li><p>is equivalent to:</p><div class="code-container" data-rel="Makefile"><figure class="iseeu highlight makefile"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">FILES := <span class="variable">$(<span class="built_in">shell</span> ls)</span></span><br></pre></td></tr></table></figure></div></li><li><p>Though I didn’t find this one used in the ics project as well, I put it here because I like this approach. lol</p></li></ul><h2 id="Pattern-Rules"><a href="#Pattern-Rules" class="headerlink" title="Pattern Rules"></a>Pattern Rules</h2><p><a class="link"   href="https://www.gnu.org/software/make/manual/html_node/Pattern-Rules.html" >GNU make manual<i class="fa-solid fa-arrow-up-right ml-[0.2em] font-light align-text-top text-[0.7em] link-icon"></i></a>:</p><blockquote><p>You define an implicit rule by writing a pattern rule. A pattern rule looks like an ordinary rule, except that its target contains the character ‘%’ (exactly one of them). The target is considered a pattern for matching file names; the ‘%’ can match any nonempty substring, while other characters match only themselves. The prerequisites likewise use ‘%’ to show how their names relate to the target name.</p></blockquote><blockquote><p>Thus, a pattern rule <code>%.o : %.c</code> says how to make any file <code>stem.o</code> from another file <code>stem.c</code>.</p></blockquote><ul><li><p>This is something quite like a super simple version of regular expression for matching files under the directory.</p></li><li><p>Example:</p><ul><li><p>Rule that compiles <code>.c</code> files into <code>.o</code> files:</p><div class="code-container" data-rel="Makefile"><figure class="iseeu highlight makefile"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="section">%.o: %.c</span></span><br><span class="line">    <span class="variable">$(CC)</span> -c <span class="variable">$(CFLAGS)</span> <span class="variable">$(CPPFLAGS)</span> <span class="variable">$&lt;</span> -o <span class="variable">$@</span></span><br></pre></td></tr></table></figure></div></li><li><p>The recipe uses the automatic variables ‘$@’ and ‘$&lt;’ to substitute the names of the target file and the source file in each case where the rule applies (see <a href="https://blog.imlast.top/2024/09/29/makefile-in-pa/#Automatic-Variables">Automatic Variables</a> down below).</p></li></ul></li></ul><h2 id="Automatic-Variables"><a href="#Automatic-Variables" class="headerlink" title="Automatic Variables"></a>Automatic Variables</h2><p><a class="link"   href="https://www.gnu.org/software/make/manual/html_node/Pattern-Rules.html" >GNU make manual<i class="fa-solid fa-arrow-up-right ml-[0.2em] font-light align-text-top text-[0.7em] link-icon"></i></a>:</p><blockquote><p>Suppose you are writing a pattern rule to compile a ‘.c’ file into a ‘.o’ file: how do you write the ‘cc’ command so that it operates on the right source file name? You cannot write the name in the recipe, because the name is different each time the implicit rule is applied.</p></blockquote><blockquote><p>What you do is use a special feature of make, the automatic variables. These variables have values computed afresh for each rule that is executed, based on the target and prerequisites of the rule. In this example, you would use <code>$@</code> for the object file name and <code>$&lt;</code> for the source file name.</p></blockquote><ul><li>Automatic variables could only be used within <strong>recipes</strong>.</li></ul><hr><ul><li>Common automatic variables table:</li></ul><table><thead><tr><th>Symbol</th><th>Meaning</th></tr></thead><tbody><tr><td><code>$@</code></td><td>The file name of the target of the rule. When there’re multiple targets, <code>$@</code> is the name of whichever target caused the rule’s recipe to be run.</td></tr><tr><td><code>$%</code></td><td>The target member name <strong>when the target is an archive member</strong>. Is empty when the target is not an archive member.(See <a class="link"   href="https://www.gnu.org/software/make/manual/html_node/Archives.html" >Using make to Update Archive Files<i class="fa-solid fa-arrow-up-right ml-[0.2em] font-light align-text-top text-[0.7em] link-icon"></i></a>)</td></tr><tr><td><code>$&lt;</code></td><td>The name of the first prerequisite. If the target got its recipe from an implicit rule, this will be the first prerequisite added by the implicit rule (see <a class="link"   href="https://www.gnu.org/software/make/manual/html_node/Implicit-Rules.html" >Using Implicit Rules<i class="fa-solid fa-arrow-up-right ml-[0.2em] font-light align-text-top text-[0.7em] link-icon"></i></a>).</td></tr><tr><td><code>$^</code></td><td>The names of all the prerequisites, with spaces between them.</td></tr><tr><td><code>$?</code></td><td>The names of all the prerequisites that are <strong>newer than the target</strong>, with spaces between them.</td></tr><tr><td>…</td><td>…</td></tr></tbody></table><h2 id="To-Be-Continued…"><a href="#To-Be-Continued…" class="headerlink" title="To Be Continued…"></a>To Be Continued…</h2>]]>
    </content>
    <id>https://blog.imlast.top/2024/09/29/makefile-in-pa/</id>
    <link href="https://blog.imlast.top/2024/09/29/makefile-in-pa/"/>
    <published>2024-09-29T08:47:02.000Z</published>
    <summary>Makefiles used in NJU-PA</summary>
    <title>Learning Makefile with PA</title>
    <updated>2024-09-29T08:47:02.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>Last</name>
    </author>
    <category term="开发记录" scheme="https://blog.imlast.top/categories/%E5%BC%80%E5%8F%91%E8%AE%B0%E5%BD%95/"/>
    <category term="TypeScript" scheme="https://blog.imlast.top/tags/TypeScript/"/>
    <category term="mqttx" scheme="https://blog.imlast.top/tags/mqttx/"/>
    <category term="protobuf" scheme="https://blog.imlast.top/tags/protobuf/"/>
    <category term="jest" scheme="https://blog.imlast.top/tags/jest/"/>
    <content>
      <![CDATA[<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><ul><li>本人在编写针对 <code>src/utils/protobuf.ts</code> 的过程中发现该测试文件已经完成，因此将其 Sync 后 Pull 到本地与本人写的测试代码进行比对，发现了一些不妥当的地方。</li><li><strong>当然，也可能是本人意见有误，望海涵。</strong></li></ul><h2 id="另：工作的必要性考虑"><a href="#另：工作的必要性考虑" class="headerlink" title="另：工作的必要性考虑"></a>另：工作的必要性考虑</h2><ul><li>由于本人参与的开源之夏项目只包含 MQTTX 项目中 avro 编码的功能开发，protobuf 功能实际并非由该项目负责，也没有义务指导我开发这部分的功能。</li><li><strong>因此，倘若认为 protobuf 部分的测试用例无须修改，或者生活工作过于忙碌分身乏术，可以直接忽略本文章以及本次本人提交的 PR。</strong></li></ul><h2 id="测试思路"><a href="#测试思路" class="headerlink" title="测试思路"></a>测试思路</h2><ul><li><p>以其中一个测试用例为例：</p><div class="code-container" data-rel="Typescript"><figure class="iseeu highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="title function_">it</span>(<span class="string">&#x27;should serialize JSON message with a protobuf schema correctly&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">const</span> resultBuffer = <span class="title function_">serializeProtobufToBuffer</span>(jsonMessage, mockProtoPath, <span class="string">&#x27;SensorData&#x27;</span>)</span><br><span class="line">    <span class="title function_">expect</span>(resultBuffer).<span class="title function_">toBeInstanceOf</span>(<span class="title class_">Buffer</span>)</span><br><span class="line"></span><br><span class="line">    <span class="keyword">const</span> root = protobuf.<span class="title function_">parse</span>(protoSchema).<span class="property">root</span></span><br><span class="line">    <span class="keyword">const</span> <span class="title class_">SensorData</span> = root.<span class="title function_">lookupType</span>(<span class="string">&#x27;SensorData&#x27;</span>)</span><br><span class="line">    <span class="keyword">const</span> decodedMessage = <span class="title class_">SensorData</span>.<span class="title function_">decode</span>(resultBuffer)</span><br><span class="line">    <span class="title function_">expect</span>(decodedMessage.<span class="title function_">toJSON</span>()).<span class="title function_">toEqual</span>(&#123;</span><br><span class="line">        <span class="attr">deviceId</span>: <span class="string">&#x27;123456&#x27;</span>,</span><br><span class="line">        <span class="attr">sensorType</span>: <span class="string">&#x27;Temperature&#x27;</span>,</span><br><span class="line">        <span class="attr">value</span>: <span class="number">22.5</span>,</span><br><span class="line">        <span class="attr">timestamp</span>: <span class="string">&#x27;16700&#x27;</span>,</span><br><span class="line">    &#125;)</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure></div></li><li><p>这里的思路很奇怪，详见下图：</p><p><img src="https://s2.loli.net/2024/09/05/CWQbiGVxa8u92Fe.png" alt="test_routine"></p></li><li><p>私以为，我们应当将<strong>相同的输入</strong>分别输入<strong>待测试的流程</strong>和<strong>保证正确的流程</strong>，并将这两个流程的输出进行比较，相同则为通过，不同则为不通过。</p></li><li><p>但是上面的这个测试流程如图所示，将<strong>待测试的编码流程</strong>产出的结果输入了<strong>解码流程</strong>，而后将其结果与一个硬编码对象进行比较，在我看来有些奇怪。</p></li><li><p>后续还有许多相似的点，不再赘述。</p></li></ul><h2 id="toHaveBeenCalledWith-的陷阱"><a href="#toHaveBeenCalledWith-的陷阱" class="headerlink" title="toHaveBeenCalledWith 的陷阱"></a><em>toHaveBeenCalledWith</em> 的陷阱</h2><h3 id="源头"><a href="#源头" class="headerlink" title="源头"></a>源头</h3><ul><li><p>注意到 <code>src/utils/protobuf.ts</code> 中的 42-43 行并未被测试覆盖到：</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">---------------------|---------|----------|---------|---------|----------------------</span><br><span class="line">File                 | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s</span><br><span class="line">---------------------|---------|----------|---------|---------|----------------------</span><br><span class="line">All files            |    23.5 |    10.33 |   19.12 |   23.85 |</span><br><span class="line">    ...</span><br><span class="line">  protobuf.ts        |   94.44 |    66.66 |     100 |   94.11 | 42-43</span><br><span class="line">    ...</span><br></pre></td></tr></table></figure></div></li><li><p>翻阅源码，42-43 及其周围代码如下：</p><div class="code-container" data-rel="Typescript"><figure class="iseeu highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// ...</span></span><br><span class="line"><span class="keyword">const</span> root = protobuf.<span class="title function_">loadSync</span>(protobufPath)</span><br><span class="line"><span class="keyword">const</span> <span class="title class_">Message</span> = root.<span class="title function_">lookupType</span>(protobufMessageName)</span><br><span class="line"><span class="keyword">const</span> <span class="title class_">MessageData</span> = <span class="title class_">Message</span>.<span class="title function_">decode</span>(payload)</span><br><span class="line"><span class="keyword">const</span> err = <span class="title class_">Message</span>.<span class="title function_">verify</span>(<span class="title class_">MessageData</span>)</span><br><span class="line"><span class="keyword">if</span> (err) &#123;</span><br><span class="line">  <span class="comment">// line 42:</span></span><br><span class="line">  logWrapper.<span class="title function_">fail</span>(<span class="string">`Unable to deserialize protobuf encoded buffer: <span class="subst">$&#123;err&#125;</span>`</span>)</span><br><span class="line">  <span class="comment">// line 43:</span></span><br><span class="line">  process.<span class="title function_">exit</span>(<span class="number">1</span>)</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// ...</span></span><br></pre></td></tr></table></figure></div></li><li><p>显然，未被触发的 branch 与 <code>Message.verify(...)</code> 函数有关。</p></li><li><p>但是，在 <code>serializeProtobufToBuffer</code> 函数中也有相同的分支，为什么它被认为受到测试覆盖了？</p></li></ul><hr><h3 id="原因"><a href="#原因" class="headerlink" title="原因"></a>原因</h3><ul><li><p>为篇幅考虑，只说结论：</p><ol><li><p><code>toHaveBeenCalledWith(...)</code> 确实用于判断某指定的 mock function 是否被调用过，<strong>但是他的判断范围是全局的！会包括该函数在其他的测试用例中被调用的次数</strong>！</p><details class="relative my-4 border border-border-color bg-second-background-color rounded-md  blue" data-header-exclude><summary class="px-4 py-2 rounded-md shadow-[0_0_2px_0_var(--shadow-color-1)] cursor-pointer not-markdown"><i class="fa-solid fa-chevron-right"></i>案例</summary><div class="content p-4 "><div class="code-container" data-rel="Typescript"><figure class="iseeu highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// <span class="doctag">BUG:</span> this would pass</span></span><br><span class="line"><span class="title function_">it</span>(<span class="string">&#x27;nothing happens&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="title function_">expect</span>(mockExit).<span class="title function_">toHaveBeenCalledWith</span>(<span class="number">1</span>)</span><br><span class="line">&#125;)</span><br><span class="line"></span><br><span class="line"><span class="title function_">it</span>(<span class="string">&#x27;should log an error and exit if message does not match schema&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">const</span> invalidMessage = <span class="title class_">Buffer</span>.<span class="title function_">from</span>(<span class="string">&#x27;&#123;&quot;invalidField&quot;: &quot;value&quot;&#125;&#x27;</span>)</span><br><span class="line">    <span class="title function_">serializeProtobufToBuffer</span>(invalidMessage, mockProtoPath, <span class="string">&#x27;SensorData&#x27;</span>)</span><br><span class="line">    <span class="title function_">expect</span>(mockExit).<span class="title function_">toHaveBeenCalledWith</span>(<span class="number">1</span>)</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure></div><ul><li>第一个测试用例将会通过，因为对于 <code>mockExit</code> 的追踪是全局的，第二个测试用例中触发的次数在第一个测试用例中也会被计算。</li></ul></div></details></li><li><p>在 <code>serializeProtobufToBuffer</code> 函数中，两个分支内 <code>logWrapper.fail()</code> 中的 error message 以相同的形式开头：<code>Message serialization error: ...</code>。<strong>配合前一个因素</strong>，导致了一个 BUG：<strong>只要其中一个 <code>logWrapper.fail()</code> 被调用，Jest 会认为两个测试用例都已通过</strong>。</p><ul><li>这种情况当然也出现在 <code>process.exit(1)</code> 上，但是影响相对较小。</li></ul></li><li><p><code>protobuf.Type.verify</code> 的行为并非预期（见下章节）。</p></li></ol></li></ul><hr><h3 id="解决方案"><a href="#解决方案" class="headerlink" title="解决方案"></a>解决方案</h3><ol><li><p>对于不同的错误处理分支，应当编写不同的 error message。</p></li><li><p>在每一个测试前重置 mock function 的状态（代码如下）</p><div class="code-container" data-rel="Typescript"><figure class="iseeu highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="title function_">describe</span>(<span class="string">&quot;...&quot;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="title function_">beforeEach</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">        jest.<span class="title function_">clearAllMocks</span>()</span><br><span class="line">    &#125;)</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure></div><div class="callout callout--titled warning mb-4 rounded-small shadow-redefine-flat bg-(--callout-bg-color) p-3 pl-1 relative flex flex-row gap-2"><div role="none" class="rounded-full self-stretch w-0.5 bg-(--callout-primary-color) shrink-0 opacity-60"></div><div class="flex flex-col gap-2"><div class="callout__title flex items-center gap-2 font-semibold tracking-tight"><i class="callout__icon fa-clock leading-none text-(--callout-primary-color) text-sm shrink-0"></i> 更新</div><div class="callout__content markdown-body flex-1 min-w-0"><p>观察到在其他测试中都调用了该函数，但是不知为何在 <code>protobuf.test.ts</code> 中没用调用。<br>猜测是 copy-paste 导致的（bushi</p></div></div></div></li><li><p>使用 <code>toHaveBeenNthCalledWith</code> <em>（不推荐，该函数的行为很奇怪）</em></p></li></ol><ul><li><strong>个人认为，第一和第二种方案都应当被实施。</strong></li></ul><hr><ul><li>但是，这并没有回答我们最开始的问题：</li></ul><blockquote><p>显然，未被触发的 branch 与 <code>Message.verify(...)</code> 函数有关。<br>但是，在 <code>serializeProtobufToBuffer</code> 函数中也有相同的分支，为什么它被认为受到测试覆盖了？</p></blockquote><ul><li>不必着急，下一篇章将会讨论这个问题。</li></ul><h2 id="protobuf-Type-verify-的意外行为"><a href="#protobuf-Type-verify-的意外行为" class="headerlink" title="protobuf.Type.verify 的意外行为"></a><em>protobuf.Type.verify</em> 的意外行为</h2><h3 id="错误表现"><a href="#错误表现" class="headerlink" title="错误表现"></a>错误表现</h3><ul><li><p>在修复了上述问题后，会发现许多原本通过的测试用例反而不通过了。他们无一例外都与 <code>protobuf.Type.verify</code> 有关。如下所示：</p><div class="code-container" data-rel="Typescript"><figure class="iseeu highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="title function_">it</span>(<span class="string">&#x27;should log an error and exit if message does not match schema&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">  <span class="keyword">const</span> invalidMessage = <span class="title class_">Buffer</span>.<span class="title function_">from</span>(<span class="string">&#x27;&#123;&quot;invalidField&quot;: &quot;value&quot;&#125;&#x27;</span>)</span><br><span class="line"></span><br><span class="line">  <span class="comment">// <span class="doctag">BUG:</span> Can not trigger line 17-18 of protobuf.ts</span></span><br><span class="line">  <span class="comment">// `protobuf.Type.verify` seems to be working in an unexpected way.</span></span><br><span class="line">  <span class="comment">// With `invalidMessage` as `raw`, no error is triggerred.</span></span><br><span class="line">  <span class="title function_">expect</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="title function_">serializeProtobufToBuffer</span>(invalidMessage, mockProtoPath, <span class="string">&#x27;SensorData&#x27;</span>)</span><br><span class="line">  &#125;).<span class="title function_">toThrow</span>()</span><br><span class="line">  <span class="title function_">expect</span>(logWrapper.<span class="property">fail</span>).<span class="title function_">toHaveBeenCalledWith</span>(</span><br><span class="line">    expect.<span class="title function_">stringMatching</span>(<span class="regexp">/Unable to serialize message to protobuf buffer:*/</span>),</span><br><span class="line">  )</span><br><span class="line">  <span class="title function_">expect</span>(mockExit).<span class="title function_">toHaveBeenCalledWith</span>(<span class="number">1</span>)</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure></div><div class="code-container" data-rel="Typescript"><figure class="iseeu highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="title function_">it</span>(<span class="string">&#x27;should log an error and exit if buffer is not valid protobuf&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">const</span> invalidBuffer = <span class="title class_">Buffer</span>.<span class="title function_">from</span>([<span class="number">0x08</span>, <span class="number">0x96</span>, <span class="number">0x01</span>]) <span class="comment">// An invalid protobuf buffer</span></span><br><span class="line"></span><br><span class="line">    <span class="comment">// <span class="doctag">BUG:</span> Can not trigger line 42-43 of protobuf.ts</span></span><br><span class="line">    <span class="comment">// `protobuf.Type.verify` seems to be working in an unexpected way.</span></span><br><span class="line">    <span class="comment">// With `invalidBuffer` as `payload`, the decoded message is &#x27;&#123;&quot;deviceId&quot;:&quot;&quot;&#125;&#x27; and no error is triggerred.</span></span><br><span class="line">    <span class="title function_">expect</span>(<span class="function">() =&gt;</span> <span class="title function_">deserializeBufferToProtobuf</span>(invalidBuffer, mockProtoPath, <span class="string">&#x27;SensorData&#x27;</span>, <span class="literal">true</span>)).<span class="title function_">toThrow</span>()</span><br><span class="line">    <span class="comment">// error message is generated by function `transformPBJSError`</span></span><br><span class="line">    <span class="title function_">expect</span>(logWrapper.<span class="property">fail</span>).<span class="title function_">toHaveBeenCalledWith</span>(expect.<span class="title function_">stringMatching</span>(<span class="regexp">/Message deserialization error:*/</span>))</span><br><span class="line">    <span class="title function_">expect</span>(mockExit).<span class="title function_">toHaveBeenCalledWith</span>(<span class="number">1</span>)</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure></div><ul><li>还有其他的测试用例无法通过，不再赘述。</li></ul></li></ul><h3 id="原因-1"><a href="#原因-1" class="headerlink" title="原因"></a>原因</h3><ul><li><p>但是，有一个测试用例触发了 17-18 行的分支，并且成功通过：</p><div class="code-container" data-rel="Typescript"><figure class="iseeu highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// INFO: only this test case can trigger line 17-18</span></span><br><span class="line"><span class="title function_">it</span>(<span class="string">&#x27;should handle verification errors from protobuf&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">const</span> invalidMessage = <span class="string">&#x27;&#123;&quot;deviceId&quot;: 123&#125;&#x27;</span> <span class="comment">// deviceId should be a string</span></span><br><span class="line">    <span class="title function_">expect</span>(<span class="function">() =&gt;</span> <span class="title function_">serializeProtobufToBuffer</span>(invalidMessage, mockProtoPath, <span class="string">&#x27;SensorData&#x27;</span>)).<span class="title function_">toThrow</span>()</span><br><span class="line">    <span class="title function_">expect</span>(logWrapper.<span class="property">fail</span>).<span class="title function_">toHaveBeenCalledWith</span>(expect.<span class="title function_">stringMatching</span>(<span class="regexp">/Message serialization error:*/</span>))</span><br><span class="line">    <span class="title function_">expect</span>(mockExit).<span class="title function_">toHaveBeenCalledWith</span>(<span class="number">1</span>)</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure></div></li><li><p>这让我产生了些许灵感，会不会是因为 <code>protobuf.Type.verify</code> 方法只能验证 Schema 中出现过的数据的<strong>类型</strong>是否正确？当出现一个完全不符合 Schema 定义的 protobuf 消息时，它会怎么处理呢？</p></li><li><p>测试如下：</p><details class="relative my-4 border border-border-color bg-second-background-color rounded-md  blue" data-header-exclude><summary class="px-4 py-2 rounded-md shadow-[0_0_2px_0_var(--shadow-color-1)] cursor-pointer not-markdown"><i class="fa-solid fa-chevron-right"></i>测试</summary><div class="content p-4 "><p>注：该测试由 ChatGPT 提供，较为冗长，建议直接快进到输出和结论部分。</p><div class="code-container" data-rel="Typescript"><figure class="iseeu highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> * <span class="keyword">as</span> protobuf <span class="keyword">from</span> <span class="string">&quot;protobufjs&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> protoSchema = <span class="string">`</span></span><br><span class="line"><span class="string">syntax = &quot;proto3&quot;;</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">message SensorData &#123;</span></span><br><span class="line"><span class="string">  string deviceId = 1; // deviceId is a required string field</span></span><br><span class="line"><span class="string">  float temperature = 2; // temperature is a required float field</span></span><br><span class="line"><span class="string">&#125;</span></span><br><span class="line"><span class="string">`</span>;</span><br><span class="line"><span class="keyword">const</span> root = protobuf.<span class="title function_">parse</span>(protoSchema).<span class="property">root</span>;</span><br><span class="line"><span class="keyword">const</span> <span class="title class_">SensorData</span> = root.<span class="title function_">lookupType</span>(<span class="string">&quot;SensorData&quot;</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// Utility function to test `verify` behavior</span></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">testVerify</span>(<span class="params"><span class="attr">data</span>: <span class="built_in">any</span></span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> err = <span class="title class_">SensorData</span>.<span class="title function_">verify</span>(data);</span><br><span class="line">  <span class="keyword">if</span> (err) &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">error</span>(<span class="string">`Verification failed: <span class="subst">$&#123;err&#125;</span>`</span>);</span><br><span class="line">  &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&quot;Verification succeeded:&quot;</span>, data);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 1. Test with Correct Data</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&quot;\nTest 1: Correct Data&quot;</span>);</span><br><span class="line"><span class="keyword">const</span> correctData = &#123; <span class="attr">deviceId</span>: <span class="string">&quot;sensor-001&quot;</span>, <span class="attr">temperature</span>: <span class="number">22.5</span> &#125;; <span class="comment">// Modify based on your schema</span></span><br><span class="line"><span class="title function_">testVerify</span>(correctData);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 2. Test with Incorrect Data Type</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&quot;\nTest 2: Incorrect Data Type&quot;</span>);</span><br><span class="line"><span class="keyword">const</span> incorrectTypeData = &#123; <span class="attr">deviceId</span>: <span class="number">123</span>, <span class="attr">temperature</span>: <span class="number">22.5</span> &#125;; <span class="comment">// deviceId should be a string</span></span><br><span class="line"><span class="title function_">testVerify</span>(incorrectTypeData);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 3. Test with Missing Required Field</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&quot;\nTest 3: Missing Required Field&quot;</span>);</span><br><span class="line"><span class="keyword">const</span> missingFieldData = &#123; <span class="attr">temperature</span>: <span class="number">22.5</span> &#125;; <span class="comment">// Missing deviceId</span></span><br><span class="line"><span class="title function_">testVerify</span>(missingFieldData);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 4. Test with Extra Field</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&quot;\nTest 4: Extra Field&quot;</span>);</span><br><span class="line"><span class="keyword">const</span> extraFieldData = &#123;</span><br><span class="line">  <span class="attr">deviceId</span>: <span class="string">&quot;sensor-001&quot;</span>,</span><br><span class="line">  <span class="attr">temperature</span>: <span class="number">22.5</span>,</span><br><span class="line">  <span class="attr">humidity</span>: <span class="number">40</span>,</span><br><span class="line">&#125;; <span class="comment">// humidity is not defined in schema</span></span><br><span class="line"><span class="title function_">testVerify</span>(extraFieldData);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 5. Test with Completely Invalid Data</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&quot;\nTest 5: Completely Invalid Data&quot;</span>);</span><br><span class="line"><span class="keyword">const</span> completelyInvalidData = &#123; <span class="attr">invalidField</span>: <span class="string">&quot;value&quot;</span> &#125;; <span class="comment">// No matching fields in schema</span></span><br><span class="line"><span class="title function_">testVerify</span>(completelyInvalidData);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 6. Test with Completely Invalid Data</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&quot;\nTest 6: Unstructured message&quot;</span>);</span><br><span class="line"><span class="keyword">const</span> notJSONMessage = <span class="string">&quot;Hello, world!&quot;</span>;</span><br><span class="line"><span class="title function_">testVerify</span>(notJSONMessage);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 7. Test with Random Buffer (Invalid Protobuf Message)</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&quot;\nTest 7: Random Buffer&quot;</span>);</span><br><span class="line"><span class="keyword">const</span> randomBuffer = <span class="title class_">Buffer</span>.<span class="title function_">from</span>([<span class="number">0x08</span>, <span class="number">0x96</span>, <span class="number">0x01</span>]);</span><br><span class="line"><span class="keyword">try</span> &#123;</span><br><span class="line">  <span class="keyword">const</span> decodedMessage = <span class="title class_">SensorData</span>.<span class="title function_">decode</span>(randomBuffer);</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&quot;Decoded message:&quot;</span>, decodedMessage);</span><br><span class="line">  <span class="title function_">testVerify</span>(decodedMessage);</span><br><span class="line">&#125; <span class="keyword">catch</span> (error) &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">error</span>(<span class="string">&quot;Decoding failed:&quot;</span>, error);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 8. Test with Stringified JSON (Invalid Protobuf Message)</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&quot;\nTest 8: Stringified JSON&quot;</span>);</span><br><span class="line"><span class="keyword">const</span> stringifiedJson = <span class="string">&#x27;&#123;&quot;deviceId&quot;: &quot;sensor-001&quot;&#125;&#x27;</span>;</span><br><span class="line"><span class="keyword">try</span> &#123;</span><br><span class="line">  <span class="keyword">const</span> decodedMessage = <span class="title class_">SensorData</span>.<span class="title function_">decode</span>(<span class="title class_">Buffer</span>.<span class="title function_">from</span>(stringifiedJson));</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&quot;Decoded message:&quot;</span>, decodedMessage);</span><br><span class="line">  <span class="title function_">testVerify</span>(decodedMessage);</span><br><span class="line">&#125; <span class="keyword">catch</span> (error) &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">error</span>(<span class="string">&quot;Decoding failed:&quot;</span>, error);</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure></div><p>输出：</p><div class="code-container" data-rel="Plaintext"><figure class="iseeu highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br></pre></td><td class="code"><pre><span class="line"> yarn start</span><br><span class="line">yarn run v1.22.22</span><br><span class="line">$ node build/index.js</span><br><span class="line"></span><br><span class="line">Test 1: Correct Data</span><br><span class="line">Verification succeeded: &#123; deviceId: &#x27;sensor-001&#x27;, temperature: 22.5 &#125;</span><br><span class="line"></span><br><span class="line">Test 2: Incorrect Data Type</span><br><span class="line">Verification failed: deviceId: string expected</span><br><span class="line"></span><br><span class="line">Test 3: Missing Required Field</span><br><span class="line">Verification succeeded: &#123; temperature: 22.5 &#125;</span><br><span class="line"></span><br><span class="line">Test 4: Extra Field</span><br><span class="line">Verification succeeded: &#123; deviceId: &#x27;sensor-001&#x27;, temperature: 22.5, humidity: 40 &#125;</span><br><span class="line"></span><br><span class="line">Test 5: Completely Invalid Data</span><br><span class="line">Verification succeeded: &#123; invalidField: &#x27;value&#x27; &#125;</span><br><span class="line"></span><br><span class="line">Test 6: Unstructured message</span><br><span class="line">Verification failed: object expected</span><br><span class="line"></span><br><span class="line">Test 7: Random Buffer</span><br><span class="line">Decoded message: SensorData &#123; deviceId: &#x27;&#x27; &#125;</span><br><span class="line">Verification succeeded: SensorData &#123; deviceId: &#x27;&#x27; &#125;</span><br><span class="line"></span><br><span class="line">Test 8: Stringified JSON</span><br><span class="line">Decoding failed: RangeError: index out of range: 3 + 100 &gt; 26</span><br><span class="line">    at indexOutOfRange (/home/last/Coding/mqtt/testProto/node_modules/protobufjs/src/reader.js:13:12)</span><br><span class="line">    at BufferReader.skip (/home/last/Coding/mqtt/testProto/node_modules/protobufjs/src/reader.js:343:19)</span><br><span class="line">    at Reader.skipType (/home/last/Coding/mqtt/testProto/node_modules/protobufjs/src/reader.js:369:18)</span><br><span class="line">    at Reader.skipType (/home/last/Coding/mqtt/testProto/node_modules/protobufjs/src/reader.js:373:22)</span><br><span class="line">    at Type.SensorData$decode [as decode] (eval at Codegen (/home/last/Coding/mqtt/testProto/node_modules/@protobufjs/codegen/index.js:50:33), &lt;anonymous&gt;:19:5)</span><br><span class="line">    at Object.&lt;anonymous&gt; (/home/last/Coding/mqtt/testProto/build/index.js:90:39)</span><br><span class="line">    at Module._compile (node:internal/modules/cjs/loader:1364:14)</span><br><span class="line">    at Module._extensions..js (node:internal/modules/cjs/loader:1422:10)</span><br><span class="line">    at Module.load (node:internal/modules/cjs/loader:1203:32)</span><br><span class="line">    at Module._load (node:internal/modules/cjs/loader:1019:12)</span><br><span class="line">Done in 0.08s.</span><br></pre></td></tr></table></figure></div></div></details></li><li><p>注意到：</p><ol><li>对于 Schema 中存在的成员，倘若变量类型错误，<code>protobuf.Type.verify</code> 会正常报错。</li><li>倘若 Message 中缺少 Schema 中存在的成员，不会报错。</li><li>倘若 Message 中存在 Schema 中不存在的成员，不会报错。</li><li><strong>倘若 Message 完全不符合 Schema 定义，不会报错。</strong></li><li><strong>对于完全不符合 protobuf 编码格式的随机 Buffer，只要不超出限制的长度就不会报错。</strong></li></ol></li><li><p>综上所述，<code>protobuf.Type.verify</code> 对于消息的检查并不严格，会遗漏很多情况。很多在测试中预期会产生报错的行为，实际不会产生报错，导致测试用例失败。</p></li></ul><hr><ul><li>以上便是我对于 <code>protobuf.Type.verify</code> 函数行为的一些测试和总结，希望我已经将问题表述清楚了。</li></ul><h3 id="解决方案-1"><a href="#解决方案-1" class="headerlink" title="解决方案"></a>解决方案</h3><ul><li>说老实话，除去直接删除相关的测试用例，将实际情况交由用户处理之外，我想不出什么方法了。</li></ul>]]>
    </content>
    <id>https://blog.imlast.top/2024/09/05/reading-mqttx-4/</id>
    <link href="https://blog.imlast.top/2024/09/05/reading-mqttx-4/"/>
    <published>2024-09-05T11:34:00.000Z</published>
    <summary>关于 MQTTX 中对于 protobuf utils 的测试用例的一些观察和意见</summary>
    <title>阅读 MQTTX 项目：Protobuf Test Case</title>
    <updated>2024-09-05T11:34:00.000Z</updated>
  </entry>
</feed>
