<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom"><title>fengkx&#39;s Blog</title><subtitle>学习之路</subtitle><link href="https://www.fengkx.top/atom.xml" rel="self"/><link href="https://www.fengkx.top/"/><updated>2026-03-01T16:09:07.668Z</updated><id>https://www.fengkx.top/</id><author><name>Vito Leung</name></author><generator uri="https://hexo.io/">Hexo</generator><follow_challenge><feedId>41883879799601156</feedId><userId>41395421484179456</userId></follow_challenge><entry><title>2025</title><link href="https://www.fengkx.top/post/year-2025/"/><id>https://www.fengkx.top/post/year-2025/</id><published>2026-02-19T11:21:14.000Z</published><updated>2026-03-01T16:09:07.668Z</updated><content type="html"><![CDATA[<p>往年都不写年度总结，今年可能是年过半半百了，也可能是过年太闲，就写一写。回顾一下2025，记录一下现在的一些想法。</p><h2 id="工作">工作</h2><p>写代码层面上逃不开的就是LLM了。回想上一年的春节，GPT刚出thinking模式不久，deepseek刚出R1——一个甚至不支持function call的模型。而现在GPT5.3已经出来了，Codex，Claude Code之类的Coding Agents写代码是真的很好。正职工作近一年甚至近两年基本上都是围绕LLM做各种「AI」能力。</p><p>各种AI功能，本质都是在做各种各样的Agents，要说2025年前的模型能力大部分「AI功能」只是一个尝试或者宣传的噱头。还记得当时光是让LLM输出合法的JSON就得花一大堆功夫在Prompt Engineering上。为了减少幻觉提升生成的准确率和效果，还得加各种各样的外围工程化的设计。</p><p>但现在聪明的模型，只要提供的上下文的内容足够完整，不需要特别的prompt输出已经非常可用了。最大的感受是随着基础模型的进步，之前做的很多工程化尝试，比如知识库RAG，各种调整prompt，设计的router agent，多Agents调用流程的操作都没有了意义。一些不那么聪明的模型，如果能有一个沙箱可以跑通完整的repl，获取错误信息重试的流程多试几次也都大概能改对。</p><p>随着模型的能力继续更新，最重要的还是提供沙箱能力，不管是写代码，还是操作浏览器，或者OpenClaw之类通用Agents，要让模型能够拿到反馈，并多次迭代。</p><h2 id="AI-Coding">AI Coding</h2><blockquote><p>當我第一眼看到 vibe coding 時，我就知道這又是個會各說各話的詞了。有些人眼中的 vibe coding 就是跟 AI 協作寫 code，有些人的則局限於「外行人用 AI coding」或者「用 AI coding完就不維護了」之類的。那既然每個人定義都不同，這個詞就沒什麼用處了，除了讓討論的雙方混淆以外，並沒有帶來任何額外的價值，所以我不會用它。<br>—— <a href="https://life.huli.tw/2026/01/03/ai-and-duck-lazy-and-fact/">https://life.huli.tw/2026/01/03/ai-and-duck-lazy-and-fact/</a></p></blockquote><p>今年是真正的开始用LLM写代码的一年。以前还停留在tab智能补全，体验下来始终不太好用，甚至妨碍括号闭合，索性关掉了。直到3月份的时候Cursor出了composer模式也就是现在的Agent Mode，可以自己分析理解需求写大段或者整个代码文件，执行命令，构建测试。第一次真正用上是在 <a href="https://github.com/fengkx/beancount-lsp">beancount-lsp</a> 里让LLM帮我实现<a href="https://0xffff.one/d/2092">拼音首字母补全项筛选的功能</a>，一次就能选型安装依赖，编写代码编译并运行成功完成度相当可以，我就立刻给Cursor充上了值。真正的感受到了生产力的大爆发，快速推进了BeanCount LSP很长时间都没搞或者搞不出来的功能。</p><p><img loading="lazy" src="https://i.see.you/2026/02/22/u4lT/pasted-image-1771775430987.webp" alt="commits graph"></p><p>在Language Server的开发中，LLM可以充当一个非常强的搜索引擎。比如很多VSCode中有的功能，像点击文件路径跳转，编辑区上面的面包屑导航，都是由LSP提供的，但我并不知道具体是协议里的哪个功能。以前可能要搜半天，如今只需向LLM描述需求即可。VSCode中补全的触发机制会受<code>wordPattern</code>影响，直接问LLM就能弄清具体实现逻辑。</p><p>而且由于本身语言服务处理的就是文本，LLM甚至不需要生成临时测试脚本去跑，仅靠自己的深度思考就能推导出正确代码。除了当搜索引擎，也常让LLM以资深用户的角度发散思考新的使用场景，同样好用。像code lens提示账户balance和padding金额这种功能就是跟LLM讨论出来的。</p><p>后面模型能力的迭代到了GPT5.2-Codex时代，已经可以完全让GPT编译出pyodide的beancount wheel。对python wheel构建完全不了解，对python的模块系统也不太熟悉，对beancount所使用的构建工具meson更是一窍不通。但Codex经过几轮报错修改的迭代之后已经能编译出可用的wheel并且在demo中成功地跑起来了。这类问题以前可能要研究好几天。</p><p>在Coding层面上，LLM对生产力的提升可以说是立竿见影。特别是这些年工作上见过不少奇葩代码后，真心觉得LLM写代码已经比人写得要好了。至少从命名上，从注释上提升是巨大的。也不会出现因为懒得定义struct tags而直接返回嵌套JSON字符串这类情况。LLM还能降低很多沟通层面的消耗。不止一次有同事在问一些SDK API的使用问题。在回复之前就通过类似deepwiki的操作就知道答案。</p><p>在让GPT编译beancount wasm过程中，遇到缺少的C/C++编译工具链，Codex一发现缺少就开始用<code>brew</code>安装。但这样的临时做法不够稳定——依赖环境、跨平台问题都容易埋坑。我立刻让它改为创建一个可复现的Dockerfile，在容器中去安装所需的依赖。这个细节体现了人的价值所在：虽然LLM能快速生成代码，但对可维护性的考量、对整体代码结构的设计，依然离不开人的介入。</p><p>现在LLM已经把软件的门槛降低了很多，将来的护城河在于服务的场景和完成度。场景上掌握数据的服务和实时通信这类有网络效应的软件服务有护城河，而完成度则是留住用户的关键，假如AI Coding 能做出来可能80分的东西，那同样的场景下决定用户能否留下的就是细节上那20分。</p><h3 id="模型，概念涌现">模型，概念涌现</h3><p>2025年各种概念涌现，MCP、ACP、A2A、Skills层出不穷，似乎每隔一段时间就有新东西&quot;炸裂&quot;。但真正产生影响的只有占据先发优势的MCP，和依靠模型通用能力的Skills。其他的ACP、A2A之类基本没有水花。streamable HTTP出来前SSE transport就一言难尽，整个协议除了tools外其他部分基本没被用起来。到现在还得看cursor/claude desktop这类client实现，用户体验完全取决于client的脸色。现在我还是觉得stdio MCP比不过一个设计优秀，信息完整的CLI，remote MCP比不过一个有优秀设计，文档详尽的API。跟MCP比起来skills设计简单而且可以按需加载，要好上不少。</p><p>各种新模型也是层出不穷，每个都说自己是SOTA或者在benchmark里达到了接近SOTA的标准。但是实际上用下来，模型之间的差距真的非常大。在干正事的时候还是得用好的模型，好的模型一次就能成功，因此节约的token和时间成本真的值得多花这些钱。更重要的是用上领先模型之后，对模型能力边界有更好的认知，有一种<strong>想象力不会被制约</strong>的感觉。但是一些并不在意后续维护，或者后台执行不在意成功与否，允许多次重试的任务用国产便宜模型也未尝不可。</p><p>一个相对有难度的自己的评测项目要比看benchmark跑分要有意义的多。<a href="https://mp.weixin.qq.com/s/VIeAQrK-V3LSXd0YEktKiQ">这个横评</a>就很不错。</p><h2 id="投资">投资</h2><p>投资是认真在学习，在做的，跟代码一样的终身事业。2025年收益率跟2024年比稍低一点，但是已经超出我的预期，我非常明白不可能每年都这样。</p><p>2025年最大的改变就是开始买入AI，年初的deepseek时刻，美股因为对算力需求恐慌大跌了一波。我开始建仓了AI上游的产业，芯片代工厂。当时的思考是下游的盈利能力不好说，但是巨头们在AI上的资本开支对上游来说是实实在在的营收。随后在川普的关税冲击等大跌的时候继续加仓。回头这是相当成功的一笔投资。但当时的我对模型能力仍然没有信心。即便是当时到处「炸裂」的deepseek，我觉得效果只是一般般，幻觉很严重。因此我的投资仍然仅限于上游中的芯片代工厂。心里想的是，代工业务的客户相对分散，也有像iPhone这类消费品的支撑，而没有买入英伟达。</p><p>转折发生在Cursor推出<a href="https://cursor.com/docs/configuration/worktrees">parallel agents</a>，它通过git worktree,同时跑不同的模型。这样我似乎就可以不用考虑模型能力，直接看结果选就好了。worktree确实有各种依赖管理问题，细节上也有待打磨，并发烧token钱包也会顶不住。但是如果成本降下来，并发跑Agent是一个范式上的变化，用法上的根本改变。</p><p>一旦推理成本持续下降，而在大部分场景下模型的能力拉不开差距，最终token的需求只会继续爆发。但因为API兼容非常轻松，迁移成本几乎为零，光靠卖API没有什么护城河。我选择开始买入上游的英伟达，并且继续看多大模型的应用层增长，加仓做出notebookLM之类应用的谷歌，看空仅靠卖API为生的企业。之后国内上市的两家LLM企业的招股书也说明大部分的利润目前还是在上游。</p><p>2026年估值越来越高，风险会更高，应该更小心，要降低预期收益。<strong>赚钱靠运气，不亏靠实力</strong>。</p><h2 id="生活">生活</h2><h3 id="读书">读书</h3><p>25年读的书可以分三类，一大类是投资相关的书，第二类是心理学，哲学的书，最后是其他一些非虚构类书籍，像是传记之类的。投资的书看了很多，大部分都是价值投资老生常谈的理论。其中<a href="https://weread.qq.com/web/bookDetail/d5932c90813ab6d60g018df9">《低风险，高回报》</a>印象最深，主要是作者提供了非常实际的操作路径。书名虽叫低风险，更准确说是低波动、高回报。作者通过各种历史数据分析，证明了低波动的股票组合更有超额收益。</p><blockquote><p>你们只需要考虑低波动率、收益和动量三个因子就能建立一个可以带来高回报和低风险的股票组合。<br>—— 《低风险高回报》</p></blockquote><p>我觉得这个理论很有道理，开始定投了场外的红利基金。</p><p>还有一本<a href="https://www.diewithzerobook.com/">《Die With Zero》</a>作者提出的花钱买体验，和记忆股息理论也印象深刻。书中提倡应该在年轻身体好的时候，多体验人生，把钱花在能够长期回忆的人生体验上。并且越早，复利效应越高。</p><blockquote><p>我希望我分享的訊息至少讓你重新思考關於人生那些很制式、傳統看法：要找個好工作，無止境地努力工作，然後在六、七十歲退休，在所謂的「黃金時期」度過自己的人生。　　我希望我分享的消息至少让你重新思考关于人生那些很制式、传统看法：要找个好工作，无止境地努力工作，然后在六、七十岁退休，在所谓的「黄金时期」度过自己的人生。　　可是我要問各位：為什麼要等到你的健康和生命活力都開始消退的時候？與其只顧著積存一大堆很可能一輩子都花不完的錢，不如現在就把自己的生活過得充實些，例如：追尋難忘的生活體驗、在你的孩子最能用到錢的時候把錢給他們、在還活著的時候把錢捐給慈善機構。這才是過人生。記住，歸根結底，人生在世，最重要的事就是要獲得回憶。所以你還在等什麼呢？<br>—— 《Die with zero》</p></blockquote><p>其他印象深刻的书还有<a href="https://zh.wikipedia.org/zh-hk/%E5%9B%BD%E5%AE%B6%E4%B8%BA%E4%BB%80%E4%B9%88%E4%BC%9A%E5%A4%B1%E8%B4%A5">《國家為什麼會失敗》</a>,《一个悲观主义者的积极思考》等，阅读时的划线和当时的一些想法都放在了<a href="https://logseq.fengkx.top/#/page/notepal%2Fbooks">logseq里</a>，时常回顾一下可能又会有不一样的想法。</p><p>2026年不打算读“心理按摩”类型的偏理论书，希望读更多具体公司的传记或者财务分析的书。</p><h3 id="旅游">旅游</h3><p>今年去了3次日本，第一次是跟朋友一起抢到清明前的便宜机票，成功在樱花季期间去了关西。</p><p><img loading="lazy" src="https://i.see.you/2026/02/22/Kq4n/sakura.jpg" alt="sakura"></p><p><img loading="lazy" src="https://i.see.you/2026/02/22/b6Ng/sakura2.jpg" alt="sakura2.jpg"></p><p><img loading="lazy" src="https://i.see.you/2026/02/22/eR5t/night-sakura.jpg" alt="night-sakura.jpg"></p><p><img loading="lazy" src="https://i.see.you/2026/02/22/1cCm/sakura-deer.jpg" alt="sakura-deer.jpg"></p><p>虽然严格来说有些早，山上的樱花都没怎么开，但树与树的体质不能一概而论(误)，京都的樱花数量和赏樱地点非常多总有开的很好的地方。最让我惊讶的是之前去过的八坂神社，河原四条这些地方随处走都能看到一条街的樱花，我之前完全没意识到路边的都是樱花树。只要时间合适，都不需要特意去找就能遇到很美的景色。</p><p>端午的时候，直飞广岛，独游了一波。去之前也没做详细攻略，只是定了去哪几个地方，怎么去，什么时候去都没有排。以至于到了广岛市区之后，坐公交去酒店都不知道怎么付钱。上车后，以为跟京都一样统一价，拖着个大箱子上车坐下，都不知道要取乘车券。过了一会发现，这里好像并不是统一价，幸亏后上车的好心人看我懵逼给我递了一张。好在广岛市区的景点，商场都很集中，基本上电车、公交甚至走路都能覆盖，而且该有的都有很适合不愿意走路的我。广岛不同于京都，基本上没有什么古建筑。广岛城也是事后重建的，但不少地方都保留了原爆的痕迹，像是被爆的建筑，被爆树木之类的。</p><p><img loading="lazy" src="https://i.see.you/2026/02/22/Dv7z/ydss.jpg" alt="itsukushima.jpg"></p><p><img loading="lazy" src="https://i.see.you/2026/02/22/Jod5/d2e2579.jpg" alt="尾道.jpg"></p><p>宫岛是广岛之行的重头戏。水上鸟居的景色、鹿、烤生蚝、200yen refill一次的酒，就值得再去一次。后面还留了一天去尾道。一座适合闲逛的小城，俯瞰濑户内海的景色很美。</p><p><img loading="lazy" src="https://i.see.you/2026/02/22/yw9I/yamazaki.jpg" alt="yamazaki.jpg"></p><p><img loading="lazy" src="https://i.see.you/2026/02/22/3bBj/yamazaki2.jpg" alt="yamazaki2.jpg"></p><p>10月份团建又去了一趟关西。这次去了山崎蒸馏所见学。进到酒厂参观酿造蒸馏，窖藏的过程，真是有趣的体验。酒窖里是真的香。最后还现场品尝了好多不同的威士忌，对比着喝是真能喝出年份酒贵有贵的道理，相对于年份少的要柔和很多。</p><p><img loading="lazy" src="https://i.see.you/2026/02/22/6hGn/spring_valley.jpg" alt="spring_valley.jpg"></p><p>除了威士忌，还喝了很多啤酒，去了各个品牌的bar。Spring Valley的一个品尝set最让人印象深刻。里面有几款酒真让我品尝出了柑橘风味而且不怎么苦。</p><p><img loading="lazy" src="https://i.see.you/2026/02/22/Gv1h/cup_noddle.jpg" alt="cup_noddle.jpg"></p><p><img loading="lazy" src="https://i.see.you/2026/02/22/cyN3/ms.jpg" alt="明石海峡大桥"></p><p>还去泡面博物馆尝试自己搭配泡面，神户参观了明石海峡大桥。不得不说日本建筑的夜晚打光水平很高，明暗对比很好看，不是超亮的灯或者高饱和度RGB。</p><p>9月份的时候还跟朋友去了泰国。泰国跟日本就很不一样。日本是干净，秩序井然，那泰国就是秩序中又很混乱，但乱的很有生机的感觉。满大街的摩托车，还有小皮卡改的双条车。街上的感觉有种小时候国内的感觉。泰国去的芭提雅曼谷两个地方，印象最深的是711便利店的密集程度我甚至觉得比日本还高。生活成本上，在外面吃饭的价格并不便宜，但是芭提雅的住宿条件和性价比要比日本高得多。按摩、在海岛上玩项目就非常便宜。</p><p><img loading="lazy" src="https://i.see.you/2026/02/22/Q6sy/thailand.jpg" alt="thailand.jpg"></p><p>广州从10多年前就开始说自己要做国际化大都市，但泰国真让我感觉是国际化大都市。街上的外国人含量很多。可能是生活成本低吧。我觉得真正的国际化并不是建了多少高楼，有多少「xx城」，而是一般服务员都能说上几句英语，即使不那么标准，各种地方不需要注册微信或者关注公众号就能买票，查看信息，不用坐个地铁还得安检。</p><p>在国内的自媒体或舆论环境下，泰国常被描述为一个不那么安全的地方，但我一直不相信这些说法，觉得都是在扯淡。这次在曼谷打车去吃饭。朋友吃到一半发现iPhone备用机找不到，我们也不确定在哪里丢了。抱着试一试的态度，通过bolt app给网约车司机打电话用蹩脚的英语问司机有没有看到手机。司机立刻就说有，并且愿意送到吃饭的地方。后面让饭店的服务员跟司机用泰语沟通地点，成功拿回手机。这待遇在国内都不一定能够有。在泰国出行基本上都是靠打车，走高速需要补高速费，这么多次都没有被骗过。遇到不确定或者沟通不顺畅的时候，司机就直接走人工通道把找回来的钱递回来。这些小细节让我对泰国的印象很好。肯定会再去的。</p><p>2026年希望可以去更多的地方玩，多见见世面积累记忆股息吧。</p><ul><li>完 -</li></ul>]]></content><summary type="html">&lt;p&gt;往年都不写年度总结，今年可能是年过半半百了，也可能是过年太闲，就写一写。回顾一下2025，记录一下现在的一些想法。&lt;/p&gt;
&lt;h2 id=&quot;工作&quot;&gt;工作&lt;/h2&gt;
&lt;p&gt;写代码层面上逃不开的就是LLM了。回想上一年的春节，GPT刚出thinking模式不久，deepseek</summary><category term="年度总结" scheme="https://www.fengkx.top/tags/%E5%B9%B4%E5%BA%A6%E6%80%BB%E7%BB%93/"/></entry><entry><title>第一次去日本旅游</title><link href="https://www.fengkx.top/post/first-japan-travel/"/><id>https://www.fengkx.top/post/first-japan-travel/</id><published>2024-11-05T11:39:37.000Z</published><updated>2026-03-01T16:09:07.664Z</updated><content type="html"><![CDATA[<h2 id="随口up">随口up</h2><p>10月去了日本旅游，团建4天假加上额外请的一天年假和周末玩了9天。上次出国还是已经是18年去的亚庇。这也是第一次去发达国家，体验非常好，要问有多好，那一句话总结就是：已经开始计划下次啥时候去了。这次坂进京出玩了大阪、京都、东京三个城市。趁现在刚回来没几天我觉得有必要记录一下，相比于记录行程流水账，我更想记录一下感受。但是不按时间轴写，又没点啥命题作文也不知道写啥，所以打算分成衣食住行+玩几部分乱序写一下见闻与感想。</p><h2 id="分开讲">分开讲</h2><h3 id="衣">衣</h3><p>走在街上，如果让我区分亚洲面孔人是不是日本人，我觉得是很难的，除了两种 CosPlay 和穿校服的学生。在日本的好几天是工作日，但经常会在我们一般认为的上学时间在外面看到穿校服的学生。几乎每天都会看到有学生在外面秋游，甚至在新干线的站台上。回想以前在学校，体育课还能上就算不错了，坐高铁去旅游属实超纲。上学时间也挺奇妙的，9点多还能在京都街头看到零零散散学生，路过一个中学4点出头，体育馆就传来练习剑道劈砍的声音，学生也陆陆续续放学了。深圳周末晚上地铁上经常能看到学生在争分夺秒做卷子，让人不得不感叹卷都。</p><p>CosPlay 的出镜率也非常高，但最让我印象深刻的是最后一天去机场的路上，在池袋可能是什么活动（下周就万圣节了）也可能是日本普通的周日，路上全是CosPlay。其中有一家四口，妈妈扮成了伏拉梅拖着一个芙丽莲女儿，女儿拿着纸皮做成的魔杖提前范起来中二病。爸爸是修塔尔克抱着另一个扮成了菲伦的小女儿。没想到会碰到一家老小一起cos的场面。日本的动漫、游戏文化是真的很流行，最后一天在东京8点多不到九点看到一个店门口排了超长的队伍，走过去一看竟然是个游戏厅。</p><h3 id="食">食</h3><p>比起讲吃了啥，有啥好吃更想记录一下一些吃饭时候的遭遇。</p><h4 id="便利店不能吃东西">便利店不能吃东西</h4><p>在日本这几天的早餐经常就是在便利店买东西吃。在中国全家/711这种有卖盒饭的便利店，基本都有座位能吃东西，但到日本的第一个早上，我们跑去一个全家买了早餐，结完账之后就发现并没有地方坐着吃。正巧碰到一个当地人在里面打热水（应该是在冲泡面或者啥东西），我们以为那个装水的地方就是吃饭的地方，就打算站那里吃。正打开吃的东西，他就跟我们说里面不能吃东西得到外面吃。出门后发现，门口的的停车码表上有好几个饮料瓶，旁边的车上也有人捧着盒饭在吃。后面查了才知道日本便利店买东西吃，如果在里面吃税率是10%，不在里面吃就是8%，很多便利店都撤掉了吃饭的地方。后来在京都碰到一家有吃饭座位的全家，结账的时候收银员就会问你是不是在这吃。</p><p><img loading="lazy" src="https://cdn.sa.net/2024/11/03/defZIYtW4pGaEXP.jpg" alt="小票"><br>左边是堂食10%，右边8%</p><h4 id="27万日元的底薪的餐厅服务员">27万日元的底薪的餐厅服务员</h4><blockquote><p>將工作視為生產，讓我們得以問出這些問題，其重要性無可言喻。然而將工作視為生產的見解，是否給予我們回答問題的憑藉，就不是那麼明朗了。逕自創造一個不同現狀的社會，運行一套不同的規則——這件事的難處，在我看來有跡可循。難就難在，人們得承認有大量工作嚴格說來屬於照護性質，而非生產性質；此外，就連擺明非關個人的工作也有照護的面向。即便我們不喜歡這個世界的樣貌，但事實是我們大部分行動裡有意識的目標（不論行動屬於生產與否），仍舊是善待他人——經常是萬中選一的他人。我們的行動鑲嵌在照護的關係中，不過多數的照護關係都需要我們多少抽離習以為常的世界。 —— 《Bullshit Jobs》</p></blockquote><p><img loading="lazy" src="https://cdn.sa.net/2024/11/03/tJwVsYIghKalkZW.jpg" alt="工资"></p><p>在日本服务非常好，服务员的精神状态也都很好。吃完饭说声delicious，会走出来跟你鞠躬。问个路即便不是工作人员也带你走一大段，走到路口。一个停车场门口两个人指挥交通，过程中手势做足让你听不懂日语也看得懂。都说日本人力成本非常贵，在东京一个吃饭的地方看到，无经验者的餐厅服务员底薪 27万日元。不知道在日本算是什么水平，但换算成人民币是相当高工资了。</p><!-- 可乐威士忌 --><h3 id="行">行</h3><p>一个是日本的电车（火车/地铁分不清楚）。它的闸机是长开的，默认选择信任，只有遇到没刷卡冲进去的人之后，才会再关上。车厢里其实没有传的或者想像中的安静，日本人也是会聊天说话的。不过有不少人会拿一本很小的书在看，这是国内很少见到的。</p><p><img loading="lazy" src="https://cdn.sa.net/2024/11/03/J5SCAWgRsKLMvzD.jpg" alt="吸烟区"><br>路上走着，红绿灯只要一闪一定过不去，不闪就能过去，不像深圳一上来就闪…  还有一个比较让我吃惊的是日本街头吸烟区竟然是排队吸烟的。一般吸烟区，一群人站一堆吸还挺正常的。但竟然看到有一个吸烟区一人一个格子，格子满了就在外面排队。</p><h3 id="住">住</h3><p>前面几天在关西住酒店没啥特别的。最后在东京住民宿就体验到了区别。最大的感受有两点，垃圾分类和极差的隔音。垃圾分类分可燃和资源型垃圾，基本上塑料，易拉罐扔资源性垃圾，纸啥的扔可燃型垃圾了。分成两类还挺好区分的，不过属于是没有开火做饭平时垃圾也就那几样。提前分好类当然是比较科学的做法，减轻了后面重新分拣的负担。日本街头很多扔垃圾的地方只能扔饮料瓶易拉罐，其他一些垃圾只能揣一路找便利店去扔，不过大家也都很守规矩。</p><p>可能是因为地震还是啥原因，房子都是木，墙很薄，隔音非常差劲，更奇葩的是离铁路非常的近。印象最深刻是江之岛电车基本上贴着居民楼走。虽说经过居民区会减速，但减速刹车本身就很吵。类似这样铁路跟居民楼挨着的情况见的很多，这是我觉得非常难以理解的点。<a href="https://www.ptt.cc/bbs/Railway/M.1553616760.A.467.html">ptt 上类似的疑惑，但并没有非常好的解答我的疑惑</a></p><h3 id="玩">玩</h3><p><img loading="lazy" src="https://cdn.sa.net/2024/11/03/SHOvLcXAKICbsRf.jpg" alt="清水寺"></p><ol><li>日本确实突出一个信任，很多地方门票都不怎么验，主要靠自觉。比如清水寺的门票就一个书签一样的纸，没有日期，门口也只是看一眼就完了。</li></ol><p><img loading="lazy" src="https://cdn.sa.net/2024/11/03/qGnDZ4cyvsOQ1Bk.jpg" alt="任天堂世界入口"></p><p><img loading="lazy" src="https://cdn.sa.net/2024/11/03/2VjvzQTHngFPqLA.jpg" alt="马里奥"></p><ol start="2"><li>USJ任天堂（马里奥）世界真的超级帅，刚进去的第一印象无敌。入口是个马里奥绿色水管样式的隧道，光线很暗，然后一出去看到马里奥世界，豁然开朗真的非常帅。</li></ol><p><img loading="lazy" src="https://cdn.sa.net/2024/11/03/lyq1W7B68naspth.jpg" alt="kyoto"><br>3. 天气很好的京都。雨后的京都街道，蓝天白云。鸭川真有鸭子。</p><p><img loading="lazy" src="https://cdn.sa.net/2024/11/03/d8rKU4WFNmDTMEx.jpg" alt="章鱼烧"><br>4. 天气不好的镰仓，天气不好，本来想去富士山改去镰仓了。虽然天气不好海边也不怎么好看，但有圣地巡礼加成比东京市区好玩多了。</p><p><img loading="lazy" src="https://cdn.sa.net/2024/11/03/z62wSMxngZKCae3.jpg" alt="tokyo tower.jpg"></p><p><img loading="lazy" src="https://cdn.sa.net/2024/11/03/1ltWRQh7pDvS4mE.jpg" alt="sakura.jpg"><br>5. 在东京芝公园拍照，两日本大妈非常热情主动找我搭话，指着旁边一棵树说让我拍这个好。树上只有孤零零一朵樱花，怕我看不到还不停强调这是sakura。我也把上午在新宿御苑拍的樱花给她们看了。日语没白学，至少听懂了，配合手机上图片和文字也是能交流的。图里左边是新宿御苑拍的，右边是芝公园拍的<a href="https://g.co/gemini/share/9fa80633b982">(来自Gemini的辨别)</a>。在日本这段时间没有墙的限制，各种AI随便用真的方便。以至于最后回到深圳的时候用谷歌翻译死活没反应，一段时间过后才反应过来被墙了。</p>]]></content><summary type="html">&lt;h2 id=&quot;随口up&quot;&gt;随口up&lt;/h2&gt;
&lt;p&gt;10月去了日本旅游，团建4天假加上额外请的一天年假和周末玩了9天。上次出国还是已经是18年去的亚庇。这也是第一次去发达国家，体验非常好，要问有多好，那一句话总结就是：已经开始计划下次啥时候去了。这次坂进京出玩了大阪、京都、东京</summary><category term="旅游" scheme="https://www.fengkx.top/tags/%E6%97%85%E6%B8%B8/"/></entry><entry><title>在 TypeScript 中获取 constructor 类型</title><link href="https://www.fengkx.top/post/get-constructor-type-from-class-in-typescript/"/><id>https://www.fengkx.top/post/get-constructor-type-from-class-in-typescript/</id><published>2023-06-16T23:43:18.000Z</published><updated>2026-03-01T16:09:07.664Z</updated><content type="html"><![CDATA[<h2 id="TL-DR">TL;DR</h2><pre class="shiki github-light" style="background-color:#fff;color:#24292e" tabindex="0"><code class="language-typescript"><span class="line"><span style="color:#D73A49">export</span><span style="color:#D73A49"> type</span><span style="color:#6F42C1"> ConstructorType</span><span style="color:#24292E">&#x3C;</span><span style="color:#6F42C1">C</span><span style="color:#D73A49"> extends</span><span style="color:#D73A49"> abstract</span><span style="color:#D73A49"> new</span><span style="color:#24292E"> (</span><span style="color:#D73A49">...</span><span style="color:#E36209">args</span><span style="color:#D73A49">:</span><span style="color:#005CC5"> any</span><span style="color:#24292E">) </span><span style="color:#D73A49">=></span><span style="color:#005CC5"> any</span><span style="color:#24292E">> </span><span style="color:#D73A49">=</span><span style="color:#24292E"> (</span></span><span class="line"><span style="color:#D73A49">  ...</span><span style="color:#E36209">args</span><span style="color:#D73A49">:</span><span style="color:#6F42C1"> ConstructorParameters</span><span style="color:#24292E">&#x3C;</span><span style="color:#6F42C1">C</span><span style="color:#24292E">></span></span><span class="line"><span style="color:#24292E">) </span><span style="color:#D73A49">=></span><span style="color:#6F42C1"> InstanceType</span><span style="color:#24292E">&#x3C;</span><span style="color:#6F42C1">C</span><span style="color:#24292E">>;</span></span></code></pre><h2 id="typeof">typeof?</h2><p>由于 JavaScript 的 class 无法很好 tree shaking，加上函数写起来非常方便，所以平时很少用 class。但最近开始折腾 <a href="https://github.com/hwchase17/langchainjs">langchainjs</a>，它基本上是跟 python 版本一一对应的，所以写得很向对象也大量用了 class。我想写一个工厂函数传入一些默认参数，却发现拿不到准确的 constructor 的类型。在 tg 群友的指点下搞懂了这里问题，关键就在于区分实例类型和 constructor 的类型。</p><pre class="shiki github-light" style="background-color:#fff;color:#24292e" tabindex="0"><code class="language-typescript"><span class="line"><span style="color:#D73A49">import</span><span style="color:#24292E"> &#123; OpenAI &#125; </span><span style="color:#D73A49">from</span><span style="color:#032F62"> "langchain/llms/openai"</span><span style="color:#24292E">;</span></span><span class="line"></span><span class="line"><span style="color:#D73A49">const</span><span style="color:#6F42C1"> createOpenAI</span><span style="color:#D73A49">:</span><span style="color:#6F42C1"> OpenAI</span><span style="color:#24292E">[</span><span style="color:#032F62">"constructor"</span><span style="color:#24292E">] </span><span style="color:#D73A49">=</span><span style="color:#24292E"> (</span><span style="color:#E36209">options</span><span style="color:#24292E">, </span><span style="color:#E36209">config</span><span style="color:#24292E">) </span><span style="color:#D73A49">=></span><span style="color:#24292E"> &#123;</span></span><span class="line"><span style="color:#6A737D">  //                       ^? Function</span></span><span class="line"><span style="color:#D73A49">  return</span><span style="color:#D73A49"> new</span><span style="color:#6F42C1"> OpenAI</span><span style="color:#24292E">(&#123; temperature: </span><span style="color:#005CC5">0</span><span style="color:#24292E">, </span><span style="color:#D73A49">...</span><span style="color:#24292E">options &#125;, config);</span></span><span class="line"><span style="color:#24292E">&#125;;</span></span></code></pre><p>这个问题可以简化成下面的代码：</p><pre class="shiki github-light" style="background-color:#fff;color:#24292e" tabindex="0"><code class="language-typescript"><span class="line"><span style="color:#D73A49">class</span><span style="color:#6F42C1"> Foo</span><span style="color:#24292E"> &#123;</span></span><span class="line"><span style="color:#D73A49">  constructor</span><span style="color:#24292E">(</span><span style="color:#E36209">foo</span><span style="color:#D73A49">:</span><span style="color:#005CC5"> string</span><span style="color:#24292E">) &#123;&#125;</span></span><span class="line"><span style="color:#24292E">&#125;</span></span><span class="line"></span><span class="line"><span style="color:#D73A49">type</span><span style="color:#6F42C1"> A</span><span style="color:#D73A49"> =</span><span style="color:#6F42C1"> Foo</span><span style="color:#24292E">;</span></span><span class="line"><span style="color:#D73A49">type</span><span style="color:#6F42C1"> B</span><span style="color:#D73A49"> =</span><span style="color:#D73A49"> typeof</span><span style="color:#24292E"> Foo;</span></span><span class="line"></span><span class="line"><span style="color:#D73A49">type</span><span style="color:#6F42C1"> C</span><span style="color:#D73A49"> =</span><span style="color:#6F42C1"> A</span><span style="color:#24292E">[</span><span style="color:#032F62">"constructor"</span><span style="color:#24292E">];</span></span><span class="line"><span style="color:#6A737D">//   ^? type C = Function</span></span></code></pre><p>如何从<code>Foo</code>通过类型运算获取到<code>(name: string) =&gt; Foo</code>这样的类型，而不是像上面一样拿到的是个<code>Function</code>。</p><p>在 TypeScript 中<code>typeof</code>除了运行态的语义外，还可以将 Value 转成 Type 用做类型运算。例如<code>const a: typeof 123 = 456</code>。但上面这个<code>A</code>跟<code>B</code>通过 alias 拿到的都是类型却很不一样。</p><p><strong>A 是实例化后的 instanceType，而 B 是构造器 constructor 的 type</strong></p><p>在运行态 JavaScript 中实例原型链上的 constructor 字段引用指向<code>Foo</code>，所以<code>A['constructor']</code>会提示<code>Function</code>类型（没有精确的参数类型）。这个 <a href="https://github.com/microsoft/TypeScript/issues/3841">GitHub issue</a> 在讨论这个行为。</p><pre class="shiki github-light" style="background-color:#fff;color:#24292e" tabindex="0"><code class="language-javascript"><span class="line"><span style="color:#D73A49">const</span><span style="color:#005CC5"> a</span><span style="color:#D73A49"> =</span><span style="color:#D73A49"> new</span><span style="color:#6F42C1"> Foo</span><span style="color:#24292E">(</span><span style="color:#032F62">"a"</span><span style="color:#24292E">);</span></span><span class="line"><span style="color:#24292E">a.</span><span style="color:#005CC5">constructor</span><span style="color:#D73A49"> ===</span><span style="color:#24292E"> Foo; </span><span style="color:#6A737D">// true</span></span></code></pre><p>再来看下面这个例子：</p><pre class="shiki github-light" style="background-color:#fff;color:#24292e" tabindex="0"><code class="language-typescript"><span class="line"><span style="color:#D73A49">class</span><span style="color:#6F42C1"> Foo</span><span style="color:#24292E"> &#123;</span></span><span class="line"><span style="color:#D73A49">  constructor</span><span style="color:#24292E">(</span><span style="color:#D73A49">public</span><span style="color:#E36209"> bar</span><span style="color:#D73A49">:</span><span style="color:#005CC5"> string</span><span style="color:#24292E">) &#123;&#125;</span></span><span class="line"><span style="color:#D73A49">  static</span><span style="color:#E36209"> baz</span><span style="color:#D73A49">:</span><span style="color:#005CC5"> 123</span><span style="color:#24292E">;</span></span><span class="line"><span style="color:#24292E">&#125;</span></span><span class="line"></span><span class="line"><span style="color:#D73A49">type</span><span style="color:#6F42C1"> A</span><span style="color:#D73A49"> =</span><span style="color:#6F42C1"> Foo</span><span style="color:#24292E">;</span></span><span class="line"><span style="color:#D73A49">type</span><span style="color:#6F42C1"> B</span><span style="color:#D73A49"> =</span><span style="color:#D73A49"> typeof</span><span style="color:#24292E"> Foo;</span></span><span class="line"></span><span class="line"><span style="color:#D73A49">type</span><span style="color:#6F42C1"> C</span><span style="color:#D73A49"> =</span><span style="color:#6F42C1"> A</span><span style="color:#24292E">[</span><span style="color:#032F62">"bar"</span><span style="color:#24292E">]; </span><span style="color:#6A737D">// string</span></span><span class="line"><span style="color:#D73A49">type</span><span style="color:#6F42C1"> D</span><span style="color:#D73A49"> =</span><span style="color:#6F42C1"> B</span><span style="color:#24292E">[</span><span style="color:#032F62">"bar"</span><span style="color:#24292E">]; </span><span style="color:#6A737D">// Property 'bar' does not exist on type 'typeof Foo'.(2339)</span></span><span class="line"><span style="color:#D73A49">type</span><span style="color:#6F42C1"> E</span><span style="color:#D73A49"> =</span><span style="color:#6F42C1"> A</span><span style="color:#24292E">[</span><span style="color:#032F62">"baz"</span><span style="color:#24292E">]; </span><span style="color:#6A737D">// Property 'baz' does not exist on type 'Foo'.(2339)</span></span><span class="line"><span style="color:#D73A49">type</span><span style="color:#6F42C1"> F</span><span style="color:#D73A49"> =</span><span style="color:#6F42C1"> B</span><span style="color:#24292E">[</span><span style="color:#032F62">"baz"</span><span style="color:#24292E">]; </span><span style="color:#6A737D">// 123</span></span></code></pre><p>Foo 中声明了两个 field，其中的 baz 是个 static filed，A 由于是实例化过的所以有 bar 没 baz，而 B 由于是 constructor 所以有 baz，没 bar</p><h1 id="infer-实现">infer 实现</h1><p>实际上 <a href="https://www.typescriptlang.org/docs/handbook/2/classes.html">TypeScript 文档</a> 中有提到<em>constructor signature</em>，构造函数类型可以写成<code>new (...args: P) =&gt; T</code></p><p>TL;DR 中的实现可以不借助 <a href="https://www.typescriptlang.org/docs/handbook/utility-types.html">内置的 Utility Types</a> 而用 infer 实现：<a href="https://www.typescriptlang.org/play?#code/JYWwDg9gTgLgBDAnmApnA3nAogRwK4CGANgDTYAeqAxvAL5wBmUEIcA5AAJKoC0VAFsSIoAdgHMUAZwD0eGMCKS2AKGVUiBSZLgAxCBAzK4xuFQgjJMKHhrQAFGDwAjIsCpwnBKAC44lqMDiAJQYtEYmlgTy7p4AXr4AjABMAMwA3MphygRO-gQ0phpacABCXoYmpub+NjD2BIkh6GFZ3GgAwtVWtdAAKsgoADztAHxwALxw7XAo5DCiACbaOXkFIigA7nB2AHR7XmKSvoEMKFBwAAoh42MnZ3AASnAA-Nt7OwdHl9djT77rADczqplG04AANCZTLrWWxQfqoQZtCAMUpeEbKaTSEwAPWeqjBAH0oQBtcLGLCUFA0QbkyrYfDEYYwnrwgZIgYo3T6EZkOwNPxWQJiH7ciAYyq8umU6gwWn0ky4QhEQbgvkChKispQCUmKUAXRBQA">Playground</a>。</p><pre class="shiki github-light" style="background-color:#fff;color:#24292e" tabindex="0"><code class="language-typescript"><span class="line"><span style="color:#D73A49">type</span><span style="color:#6F42C1"> ConstructorType</span><span style="color:#24292E">&#x3C;</span><span style="color:#6F42C1">C</span><span style="color:#24292E">> </span><span style="color:#D73A49">=</span><span style="color:#6F42C1"> C</span><span style="color:#D73A49"> extends</span><span style="color:#D73A49"> abstract</span><span style="color:#D73A49"> new</span><span style="color:#24292E"> (</span><span style="color:#D73A49">...</span><span style="color:#E36209">args</span><span style="color:#D73A49">:</span><span style="color:#D73A49"> infer</span><span style="color:#6F42C1"> P</span><span style="color:#24292E">) </span><span style="color:#D73A49">=></span><span style="color:#D73A49"> infer</span><span style="color:#6F42C1"> R</span></span><span class="line"><span style="color:#D73A49">  ?</span><span style="color:#24292E"> (</span><span style="color:#D73A49">...</span><span style="color:#E36209">args</span><span style="color:#D73A49">:</span><span style="color:#6F42C1"> P</span><span style="color:#24292E">) </span><span style="color:#D73A49">=></span><span style="color:#6F42C1"> R</span></span><span class="line"><span style="color:#D73A49">  :</span><span style="color:#005CC5"> never</span><span style="color:#24292E">;</span></span></code></pre>]]></content><summary type="html">&lt;h2 id=&quot;TL-DR&quot;&gt;TL;DR&lt;/h2&gt;
&lt;pre class=&quot;shiki github-light&quot; style=&quot;background-color:#fff;color:#24292e&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-typ</summary><category term="JavaScript" scheme="https://www.fengkx.top/tags/JavaScript/"/></entry><entry><title>用 Next.js 重写 Hexo 博客</title><link href="https://www.fengkx.top/post/rewrite-blog-with-next/"/><id>https://www.fengkx.top/post/rewrite-blog-with-next/</id><published>2023-03-12T11:03:10.000Z</published><updated>2026-03-01T16:09:07.668Z</updated><content type="html"><![CDATA[<p>在看了 <a href="https://blog.skk.moe/post/use-nextjs-and-hexo-to-rebuild-my-blog">sukka 用 Next.js 重构博客的文章</a> 后，我也有了相关的想法。正好 Next.js 13 推出了 <a href="https://beta.nextjs.org/docs/rendering/fundamentals">React Server Component 支持</a> 可以直接在 React 组件内写服务端逻辑。正好适合用来跑 hexo，于是就开始动工了。</p><h2 id="Server-Component">Server Component</h2><h3 id="什么东西会到达-client-side">什么东西会到达 client side</h3><p>Server Component 的一大亮点就是可以<a href="https://github.com/reactjs/rfcs/blob/main/text/0188-server-components.md#zero-bundle-size-components">减少 client bundle size</a>，但是究竟减少了什么东西呢。要回答这个问题，我们首先要了解什么东西会被发送到 client side。</p><p>在 Server Component 之前 Next.js 提供的 <code>getStaticProps/getServerSideProps</code> 做 SSG/SSR。这两个函数跟 Server Component 一样也是在服务端执行代码。函数返回值里的<code>props</code> 对象能在页面组件的 props 中拿到。如果你研究过 Next.js 生成的页面就会发现，除了 SSR/SSG 生成的 HTML 里有 props 相关的内容，还有一个 id 为<code>_NEXT_DATA</code>的 script tag，把服务端生成的 props 以 json 的形式又存了一遍，用来给 client side 做 <a href="https://github.com/reactwg/react-18/discussions/46#discussioncomment-846714">hydrate</a>。</p><p><img loading="lazy" src="https://vip2.loli.io/2023/03/09/12TdZV5MINS6Lqh.png" alt="bytedancenextdata"></p><p><code>getStaticProps/getServerSideProps</code> 在这里作为一个 client/server 的边界，函数内部的依赖只存在于 server，它返回值中的<code>props</code>会序列化成 JSON 送到 client side。除此之外，由于 hydrate 结果需要跟预渲染的 HTML 严格对应，所以<strong>所有 Page 组件用到的 JavaScript 都会</strong>进 client side。</p><p>对于 Server Component 而言，也存在类似的边界。这个边界从 Page 级变为了粒度更细的 Component 级，即 Server Component 和使用 <code>use client</code> 声明的 Client Component 之间的边界。而且由于 Server Compoent 不能使用绑定事件，也不能使用 hook 声明 state，所以 Server Component 不需要经过 hydrate 的过程，相关的依赖不需要发送的 client side。</p><p>但数据穿过这个边界的时候，同样也需要发送到 client side，这个过程中同样会经过序列化，所以当我们把函数之类无法序列化的东西从 Server Component 作为 props 时，会报错。这个传输的序列化格式也不是 json，而是 react 实现的<a href="https://twitter.com/dan_abramov/status/1631646794059743232">特殊格式</a>。</p><pre class="shiki github-light" style="background-color:#fff;color:#24292e" tabindex="0"><code class="language-json"><span class="line"><span style="color:#24292E">[</span><span style="color:#032F62">"$"</span><span style="color:#24292E">,</span><span style="color:#032F62">"meta"</span><span style="color:#24292E">,</span><span style="color:#005CC5">null</span><span style="color:#24292E">,&#123;</span><span style="color:#005CC5">"name"</span><span style="color:#24292E">:</span><span style="color:#032F62">"generator"</span><span style="color:#24292E">,</span><span style="color:#005CC5">"content"</span><span style="color:#24292E">:</span><span style="color:#032F62">"Hexo.js &#x26; Next.js"</span><span style="color:#24292E">&#125;]</span></span><span class="line"><span style="color:#005CC5">5</span><span style="color:#24292E">:I&#123;</span><span style="color:#005CC5">"id"</span><span style="color:#24292E">:</span><span style="color:#032F62">"9065"</span><span style="color:#24292E">,</span><span style="color:#005CC5">"name"</span><span style="color:#24292E">:</span><span style="color:#032F62">""</span><span style="color:#24292E">,</span><span style="color:#005CC5">"chunks"</span><span style="color:#24292E">:[</span><span style="color:#032F62">"313:style9-d090fe9341ff38d2"</span><span style="color:#24292E">,</span><span style="color:#032F62">"71:71-82b18e6f5d306cf7"</span><span style="color:#24292E">,</span><span style="color:#032F62">"65:65-bf32b72b8c0f255e"</span><span style="color:#24292E">,</span><span style="color:#032F62">"467:467-e4e9ede7ad711e5f"</span><span style="color:#24292E">,</span><span style="color:#032F62">"605:app/post/[slug]/page-5726ac97dd32c035"</span><span style="color:#24292E">],</span><span style="color:#005CC5">"async"</span><span style="color:#24292E">:</span><span style="color:#005CC5">false</span><span style="color:#24292E">&#125;</span></span></code></pre><p>在 Next.js ouput 目录中可以看到<code>.rsc</code>后缀的文件有类似这样的内容，<a href="https://next-rsc-notes.vercel.app/">在这个例子中</a> Client component 操作导致 Server Component 变化也会序列化成这种格式发送。但是对于静态的博客内容就不用考虑了。</p><h2 id="Hexo-集成">Hexo 集成</h2><p>毕竟有前人经验，所以过程还算顺利。一个遇到的问题是 hexo 中可能是 worker 导致引用不同 instanceof 失效，需要 patch。其他的一些问题只需把相关的包要加到 Next.js 的<code>serverComponentsExternalPackages</code>中让它不走 webpack bundle 就基本没问题了。hexo 初始化完之后基本东西都可以<code>hexo.locals</code>中取到，跟写 ejs 模版区别不大。</p><p>相比在<code>getStaticProps</code>中调用 hexo，直接在 Server component 中调用更加自然也不用手动在 client side 代码 从 JSON 构建 ReactNode 了。更细粒度的服务端组件可以更方便的在服务端一些事情。比如在 Image 组件分成两块，Server Compoent 部分可以通过 <a href="https://github.com/joe-bell/plaiceholder">plaiceholder</a> 获取图片宽高生成占位符，然后再传给 Client Side component 做 lazy load / 点击放大之类的事情。</p><h2 id="CSS-方案选择">CSS 方案选择</h2><p>Atomic CSS 些很有吸引力的好处如：更小而且不容易增长的 css 大小，不用考虑优先级问题等好处。而像 style9 这样的 CSS in JS 库更是把“将一个 CSS 拆成多个原子声明“的过程自动化了。所以最开始一版实现中，我也尝试了使用 style9 但是由于当时对它还有市面上其他支持编译期生成的 CSS in JS 库对 Next 13 的支持都有这样那样的问题。我还是用 tailwindcss 完整实现了一遍而没有用 style9。</p><h3 id="Tailwind-的问题">Tailwind 的问题</h3><p>上面提到 Server Component 传给 Client Component 的 props 会被序列化传给 client Side，而 className 也不例外。相当于每次在 className 上写一个 byte client 上就要多下两个 byte（不考虑压缩），一个在 pre rendered 的 HTML 一个在后面的 Wire 格式对象中</p><p><img loading="lazy" src="https://vip2.loli.io/2023/03/11/LSctyVfgRPGnuij.png" alt="wire format send"></p><p>style9 有 <code>incrementalClassnames</code> 选项可以生成更短的 className。除此之外还有同样的 class 优先级跟生成的 CSS 顺序有关，难以覆盖的问题，开发体验不是那么好。</p><h3 id="迁移到-style9">迁移到 style9</h3><p>后来 <a href="https://github.com/SukkaW/style9-webpack">style9-webpack</a> 支持了 Next.js 13 的 app dir，然后发现了 <a href="https://github.com/arlyon/stailwc">stailwc</a> 这样的 macro 可以进行转换，决定从 Tailwind 迁移到 style9。这样我既可以用 CSS in JS 更获得更好的开发体验，也不会丢掉 Tailwind 这样的 preset 带来统一的设计风格。</p><p>stailwc 和 style9 两者都会在编译期对代码进行操作，stailwc 是一个 swc 插件，style9 是一个 babel 插件。Next.js 中提供了 <code>experimental.swcPlugins</code> 配置，style9 则提供了相关的 Next.js 插件。我首先写出这样的代码</p><pre class="shiki github-light" style="background-color:#fff;color:#24292e" tabindex="0"><code class="language-typescript"><span class="line"><span style="color:#D73A49">const</span><span style="color:#005CC5"> styles</span><span style="color:#D73A49"> =</span><span style="color:#24292E"> style9.</span><span style="color:#6F42C1">create</span><span style="color:#24292E">(&#123;</span></span><span class="line"><span style="color:#24292E">    main: </span><span style="color:#6F42C1">tw</span><span style="color:#032F62">`mx-auto max-w-prose`</span></span><span class="line"><span style="color:#24292E">&#125;)</span></span><span class="line"></span><span class="line"><span style="color:#D73A49">&#x3C;</span><span style="color:#24292E">div className</span><span style="color:#D73A49">=</span><span style="color:#24292E">&#123;</span><span style="color:#6F42C1">styles</span><span style="color:#24292E">(</span><span style="color:#032F62">'main'</span><span style="color:#24292E">)&#125;</span><span style="color:#D73A49">></span><span style="color:#24292E">&#x3C;/</span><span style="color:#6F42C1">div</span><span style="color:#24292E">></span></span></code></pre><p>需要先过 swc 将代码转成</p><pre class="shiki github-light" style="background-color:#fff;color:#24292e" tabindex="0"><code class="language-typescript"><span class="line"><span style="color:#D73A49">const</span><span style="color:#005CC5"> styles</span><span style="color:#D73A49"> =</span><span style="color:#24292E"> style9.</span><span style="color:#6F42C1">create</span><span style="color:#24292E">(&#123;</span></span><span class="line"><span style="color:#24292E">    main: &#123; marginLeft: </span><span style="color:#032F62">'auto'</span><span style="color:#24292E">, marginRight: </span><span style="color:#032F62">'auto'</span><span style="color:#24292E">, maxWidth: </span><span style="color:#032F62">'65ch'</span><span style="color:#24292E"> &#125;</span></span><span class="line"><span style="color:#24292E">&#125;)</span></span><span class="line"></span><span class="line"><span style="color:#D73A49">&#x3C;</span><span style="color:#24292E">div className</span><span style="color:#D73A49">=</span><span style="color:#24292E">&#123;</span><span style="color:#6F42C1">styles</span><span style="color:#24292E">(</span><span style="color:#032F62">'main'</span><span style="color:#24292E">)&#125;</span><span style="color:#D73A49">></span><span style="color:#24292E">&#x3C;/</span><span style="color:#6F42C1">div</span><span style="color:#24292E">></span></span></code></pre><p>再给到 style9。这里涉及到先后问题，style9-webpack 提供的 webpack 配置把 babel 的 loader 放在前面，需要 patch 来改个顺序。</p><p>Tailwind 中除了单个的 CSS Utilities 还有”component“，类似 bluma 那样单个类名带很多样式的传统 CSS 库。它的 typography 插件就提供了一套富文本内容的样式，默认通过 class <code>prose</code>引入。这个对我来说是非常实用，毕竟写一套这样的样式很容易有遗漏的情况。而且对我来说，很容易就陷入整体观感上不好，但是不知道具体哪奇怪怎么改的情况。style9 <a href="https://github.com/johanholmerin/style9/issues/46">API 设计上鼓励写原子化 style</a>，不支持任意带 nested selector，所以并不支持用 prose。最后我保留了 Tailwind 并将 Tailwnd 配置的<code>@tailwind utilities;</code>删去，只用它的 base 和 component。utilities 部分给 stailwc 和 style9 生成。</p><p>迁移完成后，发现 <code>incrementalClassnames</code> 配置开启后，带缓存的 build 会出现 CSS 声明互相串的问题，所以没有打开。首页的 HTML 反而大了 1 ～ 2KB，但 CSS 文件大小了不少。=_=→<br>仅仅是把 utilities 部分用 stlye9 重写就能小那么多是出乎我意料的。</p><p><img loading="lazy" src="https://vip2.loli.io/2023/03/11/sI4YOyaNtZEwlHf.png" alt="tailwind"></p><p>迁移到 Next.js 可以让博客有更加现代化的<strong>开发</strong>和用户体验，在过程中体验了一把 Next.js 13 的部分新功能。虽说用到 Server Component 但是本质上网站还是个静态站，还是希望能够静态部署。目前为止用了 Cloudflare Pages 还无法部署成功。期待 next export 的功能在 app dir 中实现～</p>]]></content><summary type="html">在本篇文章中，作者讲述了如何使用 Next.js 13 的 React Server Component 来重构博客，并解释了 Server Component 如何帮助减少客户端代码包大小以及 Server Component 和 Client Component 之间的边界。此外，作者还介绍了如何集成 Hexo，并提供了一些有关 CSS 方案选择的讨论。</summary><category term="Next.js" scheme="https://www.fengkx.top/tags/Next-js/"/></entry><entry><title>针对项目定制的 ESLint Rules</title><link href="https://www.fengkx.top/post/eslint-rules-specific-to-your-project/"/><id>https://www.fengkx.top/post/eslint-rules-specific-to-your-project/</id><published>2022-06-26T23:26:12.000Z</published><updated>2026-03-01T16:09:07.664Z</updated><content type="html"><![CDATA[<p>现在写 JavaScript 项目，ESlint 可以说是标配。静态检查虽不执行代码，主要检查 AST 和文件路径，但也可以检查出很多东西。比如说 import 的文件是否真是存在，React Hooks 用法有没有问题等等。有时候一些项目写法上有特殊的限制，也可以通过 eslint 来自动化代码检查。这时就需要自定义 eslint rules。</p><h2 id="自定义-rules">自定义 rules</h2><p>rules 的写法详细的可以看<a href="https://eslint.org/docs/latest/developer-guide/working-with-rules">ESLint 文档</a>，不再赘述。<br>最重要的地方就是 export 的对象的 create 对象。在 create 对象中，key 是一个<a href="https://eslint.org/docs/latest/developer-guide/selectors">selector</a>,value 则是对应的 handler，在 handler 就可以通过<code>context.report</code>来对有问题的代码报错。语法树可以用<a href="https://astexplorer.net/">AST Explorer</a>来看。</p><h3 id="例子：小程序自定义组件中防止额外触发内置事件">例子：小程序自定义组件中防止额外触发内置事件</h3><p>在小程序自定义组件可以通过<code>this.triggerEvent('eventName', ...)</code>来触发<code>eventName</code>事件。但是有一些内置事件是不需要额外触发的，比如<code>tap</code>, <code>longpress</code> 之类的。我们就而可以写一个 rule 来禁止手动触发这些事件。<br>根据<a href="https://astexplorer.net/#/gist/7b2e6f4b59d997c8550ecca78b7eed21/37e3d4d68ed2a9b444b4ffbdee999a55892e17d9">AST</a>，可以写出下面这个 rule。</p><pre class="shiki github-light" style="background-color:#fff;color:#24292e" tabindex="0"><code class="language-javascript"><span class="line"><span style="color:#6A737D">// 内置事件</span></span><span class="line"><span style="color:#D73A49">const</span><span style="color:#005CC5"> bulidinEvents</span><span style="color:#D73A49"> =</span><span style="color:#24292E"> [</span></span><span class="line"><span style="color:#032F62">  'tap'</span><span style="color:#24292E">,</span></span><span class="line"><span style="color:#032F62">  'longpress'</span><span style="color:#24292E">,</span></span><span class="line"><span style="color:#032F62">  'longtap'</span><span style="color:#24292E">,</span></span><span class="line"><span style="color:#6A737D">  // ...</span></span><span class="line"><span style="color:#24292E">  ];</span></span><span class="line"></span><span class="line"><span style="color:#6A737D">/** </span><span style="color:#D73A49">@type</span><span style="color:#6F42C1"> &#123;import('eslint').Rule.RuleModule&#125;</span><span style="color:#6A737D"> */</span></span><span class="line"><span style="color:#005CC5">module</span><span style="color:#24292E">.</span><span style="color:#005CC5">exports</span><span style="color:#D73A49"> =</span><span style="color:#24292E"> &#123;</span></span><span class="line"><span style="color:#24292E">  meta: &#123;</span></span><span class="line"><span style="color:#24292E">    type: </span><span style="color:#032F62">'problem'</span><span style="color:#24292E">,</span></span><span class="line"></span><span class="line"><span style="color:#24292E">    docs: &#123;</span></span><span class="line"><span style="color:#24292E">      description: </span><span style="color:#032F62">'disallow trigger builtin events manually in miniprogram'</span><span style="color:#24292E">,</span></span><span class="line"><span style="color:#24292E">      category: </span><span style="color:#032F62">'Possible Errors'</span><span style="color:#24292E">,</span></span><span class="line"><span style="color:#24292E">      recommended: </span><span style="color:#005CC5">true</span><span style="color:#24292E">,</span></span><span class="line"><span style="color:#24292E">    &#125;,</span></span><span class="line"><span style="color:#24292E">    schema: [</span></span><span class="line"><span style="color:#24292E">      &#123;</span></span><span class="line"><span style="color:#24292E">        enum: [</span><span style="color:#032F62">'always'</span><span style="color:#24292E">, </span><span style="color:#032F62">'never'</span><span style="color:#24292E">],</span></span><span class="line"><span style="color:#24292E">      &#125;,</span></span><span class="line"><span style="color:#24292E">    ],</span></span><span class="line"><span style="color:#24292E">  &#125;,</span></span><span class="line"><span style="color:#6F42C1">  create</span><span style="color:#24292E">: </span><span style="color:#D73A49">function</span><span style="color:#24292E"> (</span><span style="color:#E36209">context</span><span style="color:#24292E">) &#123;</span></span><span class="line"><span style="color:#D73A49">    return</span><span style="color:#24292E"> &#123;</span></span><span class="line"><span style="color:#6F42C1">      CallExpression</span><span style="color:#24292E">: </span><span style="color:#D73A49">function</span><span style="color:#24292E"> (</span><span style="color:#E36209">node</span><span style="color:#24292E">) &#123;</span></span><span class="line"><span style="color:#D73A49">        const</span><span style="color:#24292E"> &#123; </span><span style="color:#005CC5">callee</span><span style="color:#24292E">, </span><span style="color:#E36209">arguments</span><span style="color:#24292E">: </span><span style="color:#005CC5">args</span><span style="color:#24292E"> &#125; </span><span style="color:#D73A49">=</span><span style="color:#24292E"> node;</span></span><span class="line"><span style="color:#D73A49">        if</span><span style="color:#24292E"> (callee.type </span><span style="color:#D73A49">===</span><span style="color:#032F62"> 'MemberExpression'</span><span style="color:#24292E">) &#123;</span></span><span class="line"><span style="color:#D73A49">          const</span><span style="color:#24292E"> &#123; </span><span style="color:#005CC5">object</span><span style="color:#24292E">, </span><span style="color:#005CC5">property</span><span style="color:#24292E"> &#125; </span><span style="color:#D73A49">=</span><span style="color:#24292E"> callee;</span></span><span class="line"><span style="color:#D73A49">          if</span><span style="color:#24292E"> (object.type </span><span style="color:#D73A49">===</span><span style="color:#032F62"> 'ThisExpression'</span><span style="color:#24292E">) &#123;</span></span><span class="line"><span style="color:#D73A49">            if</span><span style="color:#24292E"> (</span></span><span class="line"><span style="color:#24292E">              property.type </span><span style="color:#D73A49">===</span><span style="color:#032F62"> 'Identifier'</span><span style="color:#D73A49"> &#x26;&#x26;</span></span><span class="line"><span style="color:#24292E">              property.name </span><span style="color:#D73A49">===</span><span style="color:#032F62"> 'triggerEvent'</span></span><span class="line"><span style="color:#24292E">            ) &#123;</span></span><span class="line"><span style="color:#D73A49">              if</span><span style="color:#24292E"> (args.</span><span style="color:#005CC5">length</span><span style="color:#D73A49"> ></span><span style="color:#005CC5"> 0</span><span style="color:#24292E">) &#123;</span></span><span class="line"><span style="color:#D73A49">                const</span><span style="color:#005CC5"> firstArgrament</span><span style="color:#D73A49"> =</span><span style="color:#24292E"> args[</span><span style="color:#005CC5">0</span><span style="color:#24292E">];</span></span><span class="line"><span style="color:#D73A49">                const</span><span style="color:#005CC5"> eventName</span><span style="color:#D73A49"> =</span><span style="color:#24292E"> firstArgrament.value;</span></span><span class="line"><span style="color:#D73A49">                if</span><span style="color:#24292E"> (bulidinEvents.</span><span style="color:#6F42C1">includes</span><span style="color:#24292E">(eventName)) &#123;</span></span><span class="line"><span style="color:#24292E">                  context.</span><span style="color:#6F42C1">report</span><span style="color:#24292E">(&#123;</span></span><span class="line"><span style="color:#24292E">                    node,</span></span><span class="line"><span style="color:#24292E">                    message: </span><span style="color:#032F62">`trigger builtin events manually in miniprogram is forbidden`</span><span style="color:#24292E">,</span></span><span class="line"><span style="color:#24292E">                  &#125;);</span></span><span class="line"><span style="color:#24292E">                &#125;</span></span><span class="line"><span style="color:#24292E">              &#125;</span></span><span class="line"><span style="color:#24292E">            &#125;</span></span><span class="line"><span style="color:#24292E">          &#125;</span></span><span class="line"><span style="color:#24292E">        &#125;</span></span><span class="line"><span style="color:#24292E">      &#125;,</span></span><span class="line"><span style="color:#24292E">    &#125;;</span></span><span class="line"><span style="color:#24292E">  &#125;,</span></span><span class="line"><span style="color:#24292E">&#125;;</span></span><span class="line"></span></code></pre><h2 id="rulesdir">rulesdir</h2><p>要让 rule 生效最简单的方式就是使用<a href="https://eslint.org/docs/latest/developer-guide/working-with-rules#runtime-rules">runtime rule 的机制</a>，但是不是任何时候我们都有机会去加<code>--rules</code>。例如在一些集成度比较高的框架中比如 CRA 之类，只提供了修改 config 的方式，不支持修改命令行参数。</p><p>如果为了项目特异的规则专门发一个 plugin 的 npm 包，不仅显得有些小题大做，还不好维护。最好还是能把 rules 放在主项目内。</p><p>这里我们可以用<a href="https://github.com/not-an-aardvark/eslint-plugin-rulesdir">eslint-plugin-rulesdir</a>,它利用了 eslint plugin 的机制，在 rules getter 中获取某个目录内的所有 JS 文件作为 rules, 这样们就能在 rules 配置中通过<code>rulesdir/&lt;fileanme&gt;</code>来使用自定义的 rules 了。</p><p>比如将上面我们写的 rule 命名为<code>no-trigger-builtin-events-manually.js</code>，放到对应目录中，就而可以配置</p><pre class="shiki github-light" style="background-color:#fff;color:#24292e" tabindex="0"><code class="language-javascript"><span class="line"><span style="color:#032F62">'rulesdir/no-trigger-builtin-events-manually'</span><span style="color:#24292E">: [</span><span style="color:#032F62">'error'</span><span style="color:#24292E">, </span><span style="color:#032F62">'always'</span><span style="color:#24292E">]</span></span></code></pre><p>来禁用多余的手动事件触发。</p>]]></content><summary type="html">&lt;p&gt;现在写 JavaScript 项目，ESlint 可以说是标配。静态检查虽不执行代码，主要检查 AST 和文件路径，但也可以检查出很多东西。比如说 import 的文件是否真是存在，React Hooks 用法有没有问题等等。有时候一些项目写法上有特殊的限制，也可以通过 e</summary><category term="JavaScript" scheme="https://www.fengkx.top/tags/JavaScript/"/><category term="ESLint" scheme="https://www.fengkx.top/tags/ESLint/"/></entry><entry><title>讲不讲</title><link href="https://www.fengkx.top/post/speak-out-or-not/"/><id>https://www.fengkx.top/post/speak-out-or-not/</id><published>2021-03-28T12:09:33.000Z</published><updated>2026-03-01T16:09:07.668Z</updated><content type="html"><![CDATA[<p>发生了不少事情，看到了不少也想了不少。<a href="https://webcache.googleusercontent.com/search?q=cache:trDlAZ9WInoJ:https://bettercotton.org/where-is-better-cotton-grown/china/announcement-bci-suspends-licensing-in-western-china/+&amp;cd=5&amp;hl=en&amp;ct=clnk&amp;gl=uk">H&amp;M</a>, <a href="https://web.archive.org/web/20200312013708/https://purpose.nike.com/statement-on-xinjiang">Nike</a> 等一大堆公司和<a href="https://webcache.googleusercontent.com/search?q=cache:trDlAZ9WInoJ:https://bettercotton.org/where-is-better-cotton-grown/china/announcement-bci-suspends-licensing-in-western-china/+&amp;cd=5&amp;hl=en&amp;ct=clnk&amp;gl=uk">BCI 组织</a>对新疆棉的公告声明过了将近一年<a href="https://m.weibo.cn/status/4618225088726409">被翻出来</a>。<br>各路明星纷纷跑出来发声明与代言的品牌『割席』。其中也不乏港台明星。</p><!-- 且不说新疆的事情到底是怎么样，我倒是觉得各路明星跑出来表态让我想到了 --><h2 id="不言论的自由">不言论的自由</h2><p>我先“抛一下书包”。<br><img loading="lazy" src="https://vip2.loli.io/2023/03/08/bWusK3FZTf7pMVQ.jpg" alt="不言论的自由"><br>黄霑曾写过题为《不言論的自由》的文章，讲了在不想讲的时候可以选择不讲的珍贵。</p><blockquote><p>起初，纳粹抓共产党人的时候，<br>我沉默，因为我不是共产党人。</p><p>当他们抓社会民主主义者的时候，<br>我沉默，因为我不是社会民主主义者。</p><p>当他们抓工会成员的时候，<br>我沉默，因为我不是工会成员。</p><p>当他们抓犹太人的时候，<br>我沉默，因为我不是犹太人。</p><p>最后当他们来抓我时，<br>再也没有人站起来为我说话了。</p></blockquote><p>《起初他们》则讲了作者对自己『不讲』的忏悔。</p><p>一个写的是『不讲』，一个写的是『讲』。现在各路明星都似乎要在“大是大非”上表态，才能在中国市场混下去。其中当属港台明星的境遇最惨了。由于人为的高墙限制，港台与大陆的人所看到的东西有很大的差异，加上交流受限，在这些议题上的观点有非常大的差异。一边是不能不讲，讲了又会伤了另一边的感情，被骂人品有问题实在是难做。</p><p>黄霑讲当时欣赏不到的不言论的自由，此刻相信不少人都欣赏到了吧。如果可以选择不讲，大家都不用伤感情，都不戳破这层窗户纸，保留朦胧美，都可以选择相信他们是自己心中的样子。但《起初他们》就告诫我们『不讲』可能带来的后果。关键在于，这一切都是选择，可以选择『讲』，也可以选择『不讲』。但是现在的社会风气，利益等等因素却逼得他们不得不表态。</p><h2 id="观点不同，事实不辩">观点不同，事实不辩</h2><blockquote><p>观点不同，事实不辩</p></blockquote><p>这句话是从倪匡的一个访问中听到的，意思是观点不同可以讨论，但是事实是不容争辩的。我非常认同，这也暗含了一个讨论的前提。讨论双方对于事实的认知是对等的。</p><blockquote><p>互联网最大的问题之一大概是，懂得说「我没有足够的信息使得我可以拥有一个观点」的人实在是太少了。</p></blockquote><p>这是一位网友的微博。</p><p>在这前几天的中美高层会议，<a href="https://www.bilibili.com/video/BV15K4y1T735">一段 18 秒的视频</a>在各大官媒，自媒体传开。主要内容是我国外交官对美国致开场白严重超时，不遵守基本外交礼节的行为做出严正回应。<br>当天各大社交媒体，微信都是这一段视频和关于美国致开场白严重超时的报道。但是却没有看到”严重超时“具体究竟超了多久。我们能看到的这段 18 秒的视频，港台同胞相信也可以看到。而在 Facebook 上 ABC News 对此次会谈做了媒体视角的全程直播。这也是港台同胞们能够在 Facebook 看到的东西。很自然的，双方对事实认知的差异在双方的观点上也体现了出来。</p><p>然而抛开人为限制造成的困难不说，要改变他人心目中认知的事实是一件很难的事。可能只有自己或身边的人遭遇才能改变一个人的认知的事实。实习期间，一位同样是实习生的同事是新疆汉人。从他口中说出的东西，跟<a href="https://twitter.com/eachgo/status/1375388818631651333">这个人说的</a>(<a href="https://web.archive.org/web/20210326155540/https://twitter.com/eachgo/status/1375388818631651333">Archive</a>) 对新疆的描述上很相似。当一个人面对面的跟你说这些事情的时候，那种感觉确实是不一样的，这是跟网络世界隔着屏幕看文字报道完全不同的。同理心，或者说换位思考是实习期间另一位实习生同事常说的话。或许我们更多时候应该把注意力从“宏大叙事”转移到个人，将心比心地想一下。</p><h1 id="END">END</h1><p>至少我自己应该尊重『不讲』的选择。谨记『观点不同，事实不辩』。</p><p>。。。。。。</p><p>作为一个从小就是看香港电视，电影，听广东歌长大的广东人。衷心希望香港可以加油，再创辉煌。有位我很欣赏的歌手和作曲人林家谦的<a href="https://www.youtube.com/watch?v=pftx_43NWcI&amp;t=138s">一段关于广东歌的话</a>。</p><blockquote><p>你很难和人去争论，「点解你不喜欢广东歌」「你一定要支持广东歌的」<br>这样的争论和强迫人家去接受其实不是好事<br>你要人家接受，你要先把东西给做好</p></blockquote><p>言者无心，听者有意，我想不只是广东歌是这样的。</p>]]></content><summary type="html">&lt;p&gt;发生了不少事情，看到了不少也想了不少。&lt;a href=&quot;https://webcache.googleusercontent.com/search?q=cache:trDlAZ9WInoJ:https://bettercotton.org/where-is-better-c</summary><category term="思考人生" scheme="https://www.fengkx.top/tags/%E6%80%9D%E8%80%83%E4%BA%BA%E7%94%9F/"/></entry><entry><title>记一次 Node.js 内存优化</title><link href="https://www.fengkx.top/post/node-memory-optimize/"/><id>https://www.fengkx.top/post/node-memory-optimize/</id><published>2020-11-18T22:43:15.000Z</published><updated>2026-03-01T16:09:07.668Z</updated><content type="html"><![CDATA[<h1 id="现状-起因">现状/起因</h1><p><a href="https://github.com/fengkx/NodeRSSBot">NodeRSSBot</a>的公开 Demo <a href="https://t.me/NodeRSS_bot">@NodeRSS_bot</a> 随着项目的开源一同上线。也运行了有将近两年了。截至今日（2020/11/18）Demo 有活跃订阅源 3900+个。Demo 用 docker 部署在了一台 1C1G 的<a href="https://m.do.co/c/5d4b9ede7012">digitalocean</a>主机上，设定为每十五分钟抓取一次。由于每 15 分钟需要抓取 3k+的源地址是一个不太轻松的活，抓取的部分是单独使用一个 child_process 进行的。随着订阅数的增长，代码中的一些问题也显现了出来。近期发现<a href="https://github.com/fengkx/NodeRSSBot/blob/master/source/utils/fetch.ts">抓取进程</a>占用的内存相当的高。特别是在网络不畅通的环境（比如墙内），因为<a href="https://github.com/sindresorhus/got">got</a>的重试和超时机制，内存占用甚至会突破 Node.js 的限制而导致进程退出。</p><h1 id="排查">排查</h1><h2 id="工具">工具</h2><p>排查内存占用，首先需要找出哪些东西占用了内存。这就用到了 Chrome Dev Tool 中的 memory 面板，在这里可以加载 v8 的 heapsnapshot 并且提供总览，比较等的视图来查找内存占用（被持有）的原因。具体的用法可以看<a href="https://developers.google.com/web/tools/chrome-devtools/memory-problems/heap-snapshots?hl=zh-cn#comparison_%E8%A7%86%E5%9B%BE">Google 的官方文档</a>在这里不在赘述。但是官方文档中有部分的东西并没有说的很清楚。比如比较视图的表头几个带<code>#</code>号的含义就困扰了我一阵子。在 tg 群友的推荐下收获<a href="https://www.bitdegree.org/learn/chrome-memory-tab">这篇文章</a>是个很好的补充。</p><p>Node.js 12 中加入了<a href="https://github.com/nodejs/node/pull/27133">–heapsnapshot-signal</a>选项，可以用来生成 heapshot。</p><h2 id="String-占用">String 占用</h2><p>找出内存占用最快的方式莫过于将一大一小两个 snapshot 放到对比视图中看差值。<code>kill</code> 一番之后收获<code>.heapsnapshot</code>两个。</p><p><img loading="lazy" src="https://vip2.loli.io/2023/03/08/OU5mGByoMbc7iS1.png" alt="online-snapshot-compare"></p><p>可以看出字符串占了很多，比起别的多了一个数量级。具体来说是 feed 的 xml。下面第二大的 ArrayBufferData 也是同样的东西占的最多。具体到代码的话就是<a href="https://github.com/fengkx/NodeRSSBot/blob/4d1ebd1d212a132b16f9f843dbe5a8bfa89a251b/source/utils/fetch.ts#L39">res</a>中的<code>body</code>和<code>rawBody</code>。</p><p>找到问题了，挑选一个幸运儿，根据幸运儿的 Retainers 调整代码。直到 res 这两个属性的引用全部被 delete。本地运行<code>fetch.js</code>分析 snapshot。</p><p><img loading="lazy" src="https://vip2.loli.io/2023/03/08/nfQaNPEixms4d53.jpg" alt="sliced-string"></p><p>但是发现一个问题，rss-parser 使用的 xml2js 解析整一个 xml string。rss 中的很多 field<code>content</code>,<code>title</code>等都是这个 string 的 slice。即便除了 sliced string 没有任何别的 retainer，这个 string 也没有被 gc。由于 string 是 read only 的，所以这样的优化有助于计算性能。但是在 RSSBot 中就非常致命了。一个 feed 小的也有几百 KB，snapshot 中看到最大的能有 7M。</p><h3 id="解决">解决</h3><p>考虑到我完全不需要 rss 中除<code>title</code>, <code>link</code>之外的其他 field。所以我决定直接把 rss-parser 拿掉，换用<a href="https://github.com/tuananh/camaro">camaro</a>正如 camaro 作者在 README 中写的一样</p><blockquote><p>The whole reason of me creating this is because most of the time, I’m just interested in some of the data in the whole XML mess.</p></blockquote><p><img loading="lazy" src="https://vip2.loli.io/2023/03/08/Akz2cTOs67E9C4q.png" alt="after-camaro"></p><p>考虑到两次 snapshot 的时间间隔不一致，这样的效果也算是个不小的提升了。但是运行时间长了的话 heap 还是会上到 400MB。还有改进的空间。</p><h2 id="p-map-fastq">p-map =&gt; fastq</h2><p><img loading="lazy" src="https://vip2.loli.io/2023/03/08/I7SaPQ31i5mBus6.png" alt="array-obj-memory"></p><p><a href="https://github.com/fengkx/NodeRSSBot/blob/4d1ebd1d212a132b16f9f843dbe5a8bfa89a251b/source/utils/fetch.ts#L96">代码</a>中使用 p-map 控制请求的并发数量。本来以为不会出现如此长的数组。但阅读 p-map 源码之后发现，<code>p-map</code> 并不等于任务队列，整个数组和结果数组都会保存起来。<s>（废话）不然怎么叫 map</s></p><h3 id="解决-2">解决</h3><p>是误用了，换掉。我没有选择换成同一系列的<code>p-queue</code>, 因为他的 api 设计不太合适。然后找到了<a href="https://github.com/mcollina/fastq">fastq</a>,作者，stars，used by 看下来，再看一下代码。靠谱！果断换掉。</p><p><img loading="lazy" src="https://vip2.loli.io/2023/03/08/7HZyDJhbqoKkS35.png" alt="fastq-snapshots.png"></p><p>一切看起来是这么的美好~</p><h2 id="进一步优化">进一步优化</h2><h3 id="Manual-gc">Manual gc</h3><p>继续找占用内存的根源，发现还是有一些 xml string 阴魂不散。仔细检查 snapshot，确实应该要被 gc 才对的。于是决定手动 tigger gc。Node.js 提供 <code>--exposed-gc</code>选项。在 child_process 的 execArgv 中加上就能使用<code>global.gc</code>手动触发 gc。递归调用<code>setTimeout</code>做一个定时 3 分钟一次的 gc。效果显著，snapshot 再也没有阴魂不散的 xml string 了。<br>在本地长期运行，关闭代理模拟大量 feed 出错的情况，多次触发 snapshot，gc 后的 heap 大小表现还是很不错的。</p><p><img loading="lazy" src="https://vip2.loli.io/2023/03/08/LAgy1nSZ9DpQRdj.png" alt="prod-snapshots"></p><h3 id="检查-workQueue-情况">检查 workQueue 情况</h3><p>用上了<code>fastq</code>之后情况变成了定时向 queue 中 push 要抓取的 feed。这是检查 queue 长度（任务完成/堆积情况）就显得很重要了。受到<code>--heapsnapshot-signal</code>启发。绑定信号 handler，在接收到信号时 log 队列长度。Demo 实测<code>concurrency</code>设置在 500 的情况下，默认 40 秒请求 timeout。一轮 queue 清空约用时 3 分钟。</p><h1 id="总结感受">总结感受</h1><p>heapsnapshot 是分析内存占用的利器。但是网上的资料还是比较少，google 的官方文档也也没有讲清楚一些细节。这么走下来好像很轻松，但是不太熟悉这样的分析，实际上花了很长时间才定位出问题。必须要感谢 tg 群友的提点。</p><p>最后上一幅<a href="https://m.do.co/c/5d4b9ede7012">digitalocean</a>的资源占用图。</p><p><img loading="lazy" src="https://vip2.loli.io/2023/03/08/mPBCsE8S5MIcfWX.png" alt="memory-graph.png"></p><p>看到这一个阶梯，总算觉得时间没有白费。</p><h2 id="未来展望">未来展望</h2><p>可能会考虑别的 schedule 的方法而不是统一在一个时间内抓取全部 feed。bot 开始的时候设计就是多个用户订阅同一个 feed 不会多次请求。这当然限制了功能，比如说每个抓取间隔是全局的而不是每个 feed 可调节的。<a href="https://miniflux.app">Miniflux</a>则是将<code>(feed_id,user_id)</code>作为一个超键。提供了更丰富的功能，但是也导致会对多用户订阅同一个 feed 时会发多个请求。</p>]]></content><summary type="html">&lt;h1 id=&quot;现状-起因&quot;&gt;现状/起因&lt;/h1&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/fengkx/NodeRSSBot&quot;&gt;NodeRSSBot&lt;/a&gt;的公开 Demo &lt;a href=&quot;https://t.me/NodeRSS_bot&quot;&gt;@NodeR</summary><category term="Node.js" scheme="https://www.fengkx.top/tags/Node-js/"/><category term="HeapSnapshot" scheme="https://www.fengkx.top/tags/HeapSnapshot/"/><category term="Devtool" scheme="https://www.fengkx.top/tags/Devtool/"/></entry><entry><title>Gulp 用 Babel 降级的方式 uglify ES6 JavaScript</title><link href="https://www.fengkx.top/post/gulp-uglify-es6-babel/"/><id>https://www.fengkx.top/post/gulp-uglify-es6-babel/</id><published>2020-03-16T14:21:37.000Z</published><updated>2026-03-01T16:09:07.664Z</updated><content type="html"><![CDATA[<p>在写<a href="https://github.com/fengkx/hexo-theme-purer">Purer</a>主题时用的 Gulp，在 minify JavaScript 的时候遇到了问题。花了挺长时间折腾才找到了解决方法。马后炮的说其实并不难，但是因为 Babel，Gulp 等的版本割裂。网上搜到的方法要么已经过时了或者不全浪费了不少时间。下面用一个<a href="https://github.com/fengkx/gulp-uglify-babel-demo">实例</a>分步骤地记录一下解决方案。</p><blockquote><p><strong>本文写于 2020 年 3 月 16 日</strong></p></blockquote><pre class="shiki github-light" style="background-color:#fff;color:#24292e" tabindex="0"><code class="language-js"><span class="line"><span style="color:#24292E">(</span><span style="color:#D73A49">async</span><span style="color:#D73A49"> function</span><span style="color:#24292E"> () &#123;</span></span><span class="line"><span style="color:#D73A49">  const</span><span style="color:#005CC5"> $main</span><span style="color:#D73A49"> =</span><span style="color:#24292E"> document.</span><span style="color:#6F42C1">getElementById</span><span style="color:#24292E">(</span><span style="color:#032F62">"main"</span><span style="color:#24292E">);</span></span><span class="line"><span style="color:#D73A49">  const</span><span style="color:#005CC5"> resp</span><span style="color:#D73A49"> =</span><span style="color:#D73A49"> await</span><span style="color:#6F42C1"> fetch</span><span style="color:#24292E">(</span><span style="color:#032F62">"https://v1.jinrishici.com/all.json"</span><span style="color:#24292E">);</span></span><span class="line"><span style="color:#D73A49">  const</span><span style="color:#005CC5"> data</span><span style="color:#D73A49"> =</span><span style="color:#D73A49"> await</span><span style="color:#24292E"> resp.</span><span style="color:#6F42C1">json</span><span style="color:#24292E">();</span></span><span class="line"><span style="color:#24292E">  $main.textContent </span><span style="color:#D73A49">=</span><span style="color:#24292E"> data.content;</span></span><span class="line"><span style="color:#24292E">&#125;)();</span></span></code></pre><p>实例的代码很简单。但是用到了 ES2017 的 async 语法。使用的依赖版本可以在<a href="https://github.com/fengkx/gulp-uglify-babel-demo/blob/master/package.json">package.json</a>中看到就不赘述了。</p><h1 id="UglifyJS-只支持-ES5"><a href="https://github.com/fengkx/gulp-uglify-babel-demo/tree/fail-uglify-es6">UglifyJS 只支持 ES5</a></h1><p>我们很轻松的就可以写出类似这样的 gulpfile</p><pre class="shiki github-light" style="background-color:#fff;color:#24292e" tabindex="0"><code class="language-js"><span class="line"><span style="color:#D73A49">const</span><span style="color:#005CC5"> gulp</span><span style="color:#D73A49"> =</span><span style="color:#6F42C1"> require</span><span style="color:#24292E">(</span><span style="color:#032F62">"gulp"</span><span style="color:#24292E">);</span></span><span class="line"><span style="color:#D73A49">const</span><span style="color:#005CC5"> rename</span><span style="color:#D73A49"> =</span><span style="color:#6F42C1"> require</span><span style="color:#24292E">(</span><span style="color:#032F62">"gulp-rename"</span><span style="color:#24292E">);</span></span><span class="line"><span style="color:#D73A49">const</span><span style="color:#005CC5"> uglify</span><span style="color:#D73A49"> =</span><span style="color:#6F42C1"> require</span><span style="color:#24292E">(</span><span style="color:#032F62">"gulp-uglify"</span><span style="color:#24292E">);</span></span><span class="line"><span style="color:#D73A49">const</span><span style="color:#005CC5"> babel</span><span style="color:#D73A49"> =</span><span style="color:#6F42C1"> require</span><span style="color:#24292E">(</span><span style="color:#032F62">"gulp-babel"</span><span style="color:#24292E">);</span></span><span class="line"></span><span class="line"><span style="color:#24292E">gulp.</span><span style="color:#6F42C1">task</span><span style="color:#24292E">(</span><span style="color:#032F62">"js"</span><span style="color:#24292E">, () </span><span style="color:#D73A49">=></span><span style="color:#24292E"> &#123;</span></span><span class="line"><span style="color:#D73A49">  return</span><span style="color:#24292E"> gulp</span></span><span class="line"><span style="color:#24292E">    .</span><span style="color:#6F42C1">src</span><span style="color:#24292E">(</span><span style="color:#032F62">"main.js"</span><span style="color:#24292E">)</span></span><span class="line"><span style="color:#24292E">    .</span><span style="color:#6F42C1">pipe</span><span style="color:#24292E">(</span><span style="color:#6F42C1">uglify</span><span style="color:#24292E">())</span></span><span class="line"><span style="color:#24292E">    .</span><span style="color:#6F42C1">pipe</span><span style="color:#24292E">(</span><span style="color:#6F42C1">rename</span><span style="color:#24292E">(&#123; suffix: </span><span style="color:#032F62">".min"</span><span style="color:#24292E"> &#125;))</span></span><span class="line"><span style="color:#24292E">    .</span><span style="color:#6F42C1">pipe</span><span style="color:#24292E">(gulp.</span><span style="color:#6F42C1">dest</span><span style="color:#24292E">(</span><span style="color:#032F62">"."</span><span style="color:#24292E">));</span></span><span class="line"><span style="color:#24292E">&#125;);</span></span><span class="line"></span><span class="line"><span style="color:#24292E">gulp.</span><span style="color:#6F42C1">task</span><span style="color:#24292E">(</span><span style="color:#032F62">"default"</span><span style="color:#24292E">, gulp.</span><span style="color:#6F42C1">parallel</span><span style="color:#24292E">(</span><span style="color:#032F62">"js"</span><span style="color:#24292E">));</span></span></code></pre><p>一跑就会发现 UglifyJS 报错。</p><pre class="shiki github-light" style="background-color:#fff;color:#24292e" tabindex="0"><code class="language-js"><span class="line"><span style="color:#24292E">[</span><span style="color:#005CC5">13</span><span style="color:#24292E">:</span><span style="color:#005CC5">15</span><span style="color:#24292E">:</span><span style="color:#005CC5">26</span><span style="color:#24292E">] </span><span style="color:#6F42C1">GulpUglifyError</span><span style="color:#24292E">: unable to minify JavaScript</span></span><span class="line"><span style="color:#24292E">Caused </span><span style="color:#6F42C1">by</span><span style="color:#24292E">: </span><span style="color:#6F42C1">SyntaxError</span><span style="color:#24292E">: Unexpected </span><span style="color:#6F42C1">token</span><span style="color:#24292E">: keyword «</span><span style="color:#D73A49">function</span><span style="color:#24292E">», </span><span style="color:#6F42C1">expected</span><span style="color:#24292E">: </span><span style="color:#6F42C1">punc</span><span style="color:#24292E"> «)»</span></span></code></pre><p><code>gulp-uglify</code>使用的<a href="https://github.com/mishoo/UglifyJS2">UglifyJS</a>只支持 ES5。</p><p>有两个方法解决</p><ol><li>换用<a href="https://www.npmjs.com/package/gulp-uglify-es">gulp-uglify-es</a>它使用<a href="https://terser.org/">terser</a>支持 ES6+语法压缩。</li><li>先用 Babel 降级</li></ol><p>如果不用考虑兼容性问题，使用第一种方法就不需要往下看了。<s>如果我早点知道的话就不会花时间去折腾了</s></p><p>我当时很自然的想到用 Babel 降级。</p><p>引入 Babel 之后的 gulpfile 长这样。</p><pre class="shiki github-light" style="background-color:#fff;color:#24292e" tabindex="0"><code class="language-js"><span class="line"><span style="color:#D73A49">const</span><span style="color:#005CC5"> gulp</span><span style="color:#D73A49"> =</span><span style="color:#6F42C1"> require</span><span style="color:#24292E">(</span><span style="color:#032F62">"gulp"</span><span style="color:#24292E">);</span></span><span class="line"><span style="color:#D73A49">const</span><span style="color:#005CC5"> rename</span><span style="color:#D73A49"> =</span><span style="color:#6F42C1"> require</span><span style="color:#24292E">(</span><span style="color:#032F62">"gulp-rename"</span><span style="color:#24292E">);</span></span><span class="line"><span style="color:#D73A49">const</span><span style="color:#005CC5"> uglify</span><span style="color:#D73A49"> =</span><span style="color:#6F42C1"> require</span><span style="color:#24292E">(</span><span style="color:#032F62">"gulp-uglify"</span><span style="color:#24292E">);</span></span><span class="line"><span style="color:#D73A49">const</span><span style="color:#005CC5"> babel</span><span style="color:#D73A49"> =</span><span style="color:#6F42C1"> require</span><span style="color:#24292E">(</span><span style="color:#032F62">"gulp-babel"</span><span style="color:#24292E">);</span></span><span class="line"></span><span class="line"><span style="color:#24292E">gulp.</span><span style="color:#6F42C1">task</span><span style="color:#24292E">(</span><span style="color:#032F62">"js"</span><span style="color:#24292E">, () </span><span style="color:#D73A49">=></span><span style="color:#24292E"> &#123;</span></span><span class="line"><span style="color:#D73A49">  return</span><span style="color:#24292E"> gulp</span></span><span class="line"><span style="color:#24292E">    .</span><span style="color:#6F42C1">src</span><span style="color:#24292E">(</span><span style="color:#032F62">"main.js"</span><span style="color:#24292E">)</span></span><span class="line"><span style="color:#24292E">    .</span><span style="color:#6F42C1">pipe</span><span style="color:#24292E">(</span></span><span class="line"><span style="color:#6F42C1">      babel</span><span style="color:#24292E">(&#123;</span></span><span class="line"><span style="color:#24292E">        presets: [</span><span style="color:#032F62">"@babel/preset-env"</span><span style="color:#24292E">],</span></span><span class="line"><span style="color:#24292E">      &#125;)</span></span><span class="line"><span style="color:#24292E">    )</span></span><span class="line"><span style="color:#24292E">    .</span><span style="color:#6F42C1">pipe</span><span style="color:#24292E">(</span><span style="color:#6F42C1">uglify</span><span style="color:#24292E">())</span></span><span class="line"><span style="color:#24292E">    .</span><span style="color:#6F42C1">pipe</span><span style="color:#24292E">(</span><span style="color:#6F42C1">rename</span><span style="color:#24292E">(&#123; suffix: </span><span style="color:#032F62">".min"</span><span style="color:#24292E"> &#125;))</span></span><span class="line"><span style="color:#24292E">    .</span><span style="color:#6F42C1">pipe</span><span style="color:#24292E">(gulp.</span><span style="color:#6F42C1">dest</span><span style="color:#24292E">(</span><span style="color:#032F62">"."</span><span style="color:#24292E">));</span></span><span class="line"><span style="color:#24292E">&#125;);</span></span><span class="line"></span><span class="line"><span style="color:#24292E">gulp.</span><span style="color:#6F42C1">task</span><span style="color:#24292E">(</span><span style="color:#032F62">"default"</span><span style="color:#24292E">, gulp.</span><span style="color:#6F42C1">parallel</span><span style="color:#24292E">(</span><span style="color:#032F62">"js"</span><span style="color:#24292E">));</span></span></code></pre><h1 id="async-导致的-Babel-Polyfill-缺失"><a href="https://github.com/fengkx/gulp-uglify-babel-demo/tree/fail-async-polyfill">async 导致的 Babel Polyfill 缺失</a></h1><p>gulp build 没有报错。但是假如你也和例子中一样使用了<code>async</code>的话，运行的时候浏览器会报错。</p><pre class="shiki github-light" style="background-color:#fff;color:#24292e" tabindex="0"><code class="language-js"><span class="line"><span style="color:#6F42C1">ReferenceError</span><span style="color:#24292E">: regeneratorRuntime is not defined</span></span></code></pre><p>网上找到的信息多半是安装<code>babel-polyfill</code>,也有说要安装<code>transform-runtime</code>等等方法配置 babel。但是<code>babel-polyfill</code>已经<a href="https://babeljs.io/docs/en/babel-polyfill">Deprecated</a>了，为了跟得上时代我们还是得跟官方文档，在<a href="https://babeljs.io/docs/en/babel-preset-env#usebuiltins">babel-preset-env 的官方文档</a>就能找了正确的配置方法。我们要引入<code>core-js</code>。</p><p>安装好 core-js</p><pre class="shiki github-light" style="background-color:#fff;color:#24292e" tabindex="0"><code class="language-shell"><span class="line"><span style="color:#6F42C1">npm</span><span style="color:#032F62"> install</span><span style="color:#032F62"> core-js@3</span><span style="color:#005CC5"> --save</span></span></code></pre><p>并且更改 gulpfile 中的 babel options</p><pre class="shiki github-light" style="background-color:#fff;color:#24292e" tabindex="0"><code class="language-js"><span class="line"><span style="color:#24292E">&#123;</span></span><span class="line"><span style="color:#6F42C1">    presets</span><span style="color:#24292E">: [</span></span><span class="line"><span style="color:#24292E">        [</span><span style="color:#032F62">'@babel/preset-env'</span><span style="color:#24292E">, &#123; useBuiltIns: </span><span style="color:#032F62">'usage'</span><span style="color:#24292E">, corejs: </span><span style="color:#005CC5">3</span><span style="color:#24292E"> &#125;]</span></span><span class="line"><span style="color:#24292E">    ],</span></span><span class="line"><span style="color:#24292E">&#125;</span></span></code></pre><h1 id="浏览器-Require-缺失"><a href="https://github.com/fengkx/gulp-uglify-babel-demo/tree/fail-require-not-defined">浏览器 Require 缺失</a></h1><p>一通操作下来，浏览器仍然会在运行时报错</p><pre class="shiki github-light" style="background-color:#fff;color:#24292e" tabindex="0"><code class="language-js"><span class="line"><span style="color:#6F42C1">ReferenceError</span><span style="color:#24292E">: require is not defined</span></span></code></pre><p>说到 require 自然会想到<a href="http://browserify.org/">Browserify</a>。于是依照<a href="https://github.com/babel/babelify">文档借助 Babelify </a>我们很自然的写出类似这样的 gulpfile</p><pre class="shiki github-light" style="background-color:#fff;color:#24292e" tabindex="0"><code class="language-js"><span class="line"><span style="color:#D73A49">const</span><span style="color:#005CC5"> gulp</span><span style="color:#D73A49"> =</span><span style="color:#6F42C1"> require</span><span style="color:#24292E">(</span><span style="color:#032F62">"gulp"</span><span style="color:#24292E">);</span></span><span class="line"><span style="color:#D73A49">const</span><span style="color:#005CC5"> rename</span><span style="color:#D73A49"> =</span><span style="color:#6F42C1"> require</span><span style="color:#24292E">(</span><span style="color:#032F62">"gulp-rename"</span><span style="color:#24292E">);</span></span><span class="line"><span style="color:#D73A49">const</span><span style="color:#005CC5"> uglify</span><span style="color:#D73A49"> =</span><span style="color:#6F42C1"> require</span><span style="color:#24292E">(</span><span style="color:#032F62">"gulp-uglify"</span><span style="color:#24292E">);</span></span><span class="line"><span style="color:#D73A49">const</span><span style="color:#005CC5"> babel</span><span style="color:#D73A49"> =</span><span style="color:#6F42C1"> require</span><span style="color:#24292E">(</span><span style="color:#032F62">"gulp-babel"</span><span style="color:#24292E">);</span></span><span class="line"><span style="color:#D73A49">const</span><span style="color:#005CC5"> browserify</span><span style="color:#D73A49"> =</span><span style="color:#6F42C1"> require</span><span style="color:#24292E">(</span><span style="color:#032F62">"browserify"</span><span style="color:#24292E">);</span></span><span class="line"></span><span class="line"><span style="color:#24292E">gulp.</span><span style="color:#6F42C1">task</span><span style="color:#24292E">(</span><span style="color:#032F62">"js"</span><span style="color:#24292E">, () </span><span style="color:#D73A49">=></span><span style="color:#24292E"> &#123;</span></span><span class="line"><span style="color:#D73A49">  return</span><span style="color:#6F42C1"> browserify</span><span style="color:#24292E">(</span><span style="color:#032F62">"main.js"</span><span style="color:#24292E">)</span></span><span class="line"><span style="color:#24292E">    .</span><span style="color:#6F42C1">transform</span><span style="color:#24292E">(</span><span style="color:#032F62">"babelify"</span><span style="color:#24292E">, &#123;</span></span><span class="line"><span style="color:#24292E">      presets: [[</span><span style="color:#032F62">"@babel/preset-env"</span><span style="color:#24292E">, &#123; useBuiltIns: </span><span style="color:#032F62">"usage"</span><span style="color:#24292E">, corejs: </span><span style="color:#005CC5">3</span><span style="color:#24292E"> &#125;]],</span></span><span class="line"><span style="color:#24292E">    &#125;)</span></span><span class="line"><span style="color:#24292E">    .</span><span style="color:#6F42C1">bundle</span><span style="color:#24292E">()</span></span><span class="line"><span style="color:#24292E">    .</span><span style="color:#6F42C1">pipe</span><span style="color:#24292E">(</span><span style="color:#6F42C1">uglify</span><span style="color:#24292E">())</span></span><span class="line"><span style="color:#24292E">    .</span><span style="color:#6F42C1">pipe</span><span style="color:#24292E">(</span><span style="color:#6F42C1">rename</span><span style="color:#24292E">(&#123; suffix: </span><span style="color:#032F62">".min"</span><span style="color:#24292E"> &#125;))</span></span><span class="line"><span style="color:#24292E">    .</span><span style="color:#6F42C1">pipe</span><span style="color:#24292E">(gulp.</span><span style="color:#6F42C1">dest</span><span style="color:#24292E">(</span><span style="color:#032F62">"."</span><span style="color:#24292E">));</span></span><span class="line"><span style="color:#24292E">&#125;);</span></span><span class="line"></span><span class="line"><span style="color:#24292E">gulp.</span><span style="color:#6F42C1">task</span><span style="color:#24292E">(</span><span style="color:#032F62">"default"</span><span style="color:#24292E">, gulp.</span><span style="color:#6F42C1">parallel</span><span style="color:#24292E">(</span><span style="color:#032F62">"js"</span><span style="color:#24292E">));</span></span></code></pre><p>build 一下遇到了这样奇怪的错误，</p><pre class="shiki github-light" style="background-color:#fff;color:#24292e" tabindex="0"><code class="language-js"><span class="line"><span style="color:#6F42C1">TypeError</span><span style="color:#24292E">: file.isNull is not a </span><span style="color:#D73A49">function</span></span></code></pre><h1 id="Vinyl-stream-不兼容"><a href="https://github.com/fengkx/gulp-uglify-babel-demo/tree/fail-gulp-use-vinyl">Vinyl stream 不兼容</a></h1><p>问题出在 Browserify 的流和 Gulp 的流不兼容。Gulp 的流使用的是<a href="https://gulpjs.com/docs/en/api/vinyl">Vinyl</a>,而 browserify 使用的 node fs 的流。我们需要额外做一些转换。</p><p>安装相关的包。</p><pre class="shiki github-light" style="background-color:#fff;color:#24292e" tabindex="0"><code class="language-shell"><span class="line"><span style="color:#6F42C1">npm</span><span style="color:#032F62"> i</span><span style="color:#005CC5"> -D</span><span style="color:#032F62"> vinyl-source-stream</span></span><span class="line"><span style="color:#6F42C1">npm</span><span style="color:#032F62"> i</span><span style="color:#005CC5"> -D</span><span style="color:#032F62"> vinyl-buffer</span></span></code></pre><p>最终 gulpfile</p><pre class="shiki github-light" style="background-color:#fff;color:#24292e" tabindex="0"><code class="language-js"><span class="line"><span style="color:#D73A49">const</span><span style="color:#005CC5"> gulp</span><span style="color:#D73A49"> =</span><span style="color:#6F42C1"> require</span><span style="color:#24292E">(</span><span style="color:#032F62">"gulp"</span><span style="color:#24292E">);</span></span><span class="line"><span style="color:#D73A49">const</span><span style="color:#005CC5"> rename</span><span style="color:#D73A49"> =</span><span style="color:#6F42C1"> require</span><span style="color:#24292E">(</span><span style="color:#032F62">"gulp-rename"</span><span style="color:#24292E">);</span></span><span class="line"><span style="color:#D73A49">const</span><span style="color:#005CC5"> uglify</span><span style="color:#D73A49"> =</span><span style="color:#6F42C1"> require</span><span style="color:#24292E">(</span><span style="color:#032F62">"gulp-uglify"</span><span style="color:#24292E">);</span></span><span class="line"><span style="color:#D73A49">const</span><span style="color:#005CC5"> babel</span><span style="color:#D73A49"> =</span><span style="color:#6F42C1"> require</span><span style="color:#24292E">(</span><span style="color:#032F62">"gulp-babel"</span><span style="color:#24292E">);</span></span><span class="line"><span style="color:#D73A49">const</span><span style="color:#005CC5"> browserify</span><span style="color:#D73A49"> =</span><span style="color:#6F42C1"> require</span><span style="color:#24292E">(</span><span style="color:#032F62">"browserify"</span><span style="color:#24292E">);</span></span><span class="line"><span style="color:#D73A49">const</span><span style="color:#005CC5"> source</span><span style="color:#D73A49"> =</span><span style="color:#6F42C1"> require</span><span style="color:#24292E">(</span><span style="color:#032F62">"vinyl-source-stream"</span><span style="color:#24292E">);</span></span><span class="line"><span style="color:#D73A49">const</span><span style="color:#005CC5"> buffer</span><span style="color:#D73A49"> =</span><span style="color:#6F42C1"> require</span><span style="color:#24292E">(</span><span style="color:#032F62">"vinyl-buffer"</span><span style="color:#24292E">);</span></span><span class="line"></span><span class="line"><span style="color:#24292E">gulp.</span><span style="color:#6F42C1">task</span><span style="color:#24292E">(</span><span style="color:#032F62">"js"</span><span style="color:#24292E">, () </span><span style="color:#D73A49">=></span><span style="color:#24292E"> &#123;</span></span><span class="line"><span style="color:#D73A49">  return</span><span style="color:#6F42C1"> browserify</span><span style="color:#24292E">(</span><span style="color:#032F62">"main.js"</span><span style="color:#24292E">)</span></span><span class="line"><span style="color:#24292E">    .</span><span style="color:#6F42C1">transform</span><span style="color:#24292E">(</span><span style="color:#032F62">"babelify"</span><span style="color:#24292E">, &#123;</span></span><span class="line"><span style="color:#24292E">      presets: [[</span><span style="color:#032F62">"@babel/preset-env"</span><span style="color:#24292E">, &#123; useBuiltIns: </span><span style="color:#032F62">"usage"</span><span style="color:#24292E">, corejs: </span><span style="color:#005CC5">3</span><span style="color:#24292E"> &#125;]],</span></span><span class="line"><span style="color:#24292E">    &#125;)</span></span><span class="line"><span style="color:#24292E">    .</span><span style="color:#6F42C1">bundle</span><span style="color:#24292E">()</span></span><span class="line"><span style="color:#24292E">    .</span><span style="color:#6F42C1">pipe</span><span style="color:#24292E">(</span><span style="color:#6F42C1">source</span><span style="color:#24292E">(</span><span style="color:#032F62">"main.js"</span><span style="color:#24292E">))</span></span><span class="line"><span style="color:#24292E">    .</span><span style="color:#6F42C1">pipe</span><span style="color:#24292E">(</span><span style="color:#6F42C1">buffer</span><span style="color:#24292E">())</span></span><span class="line"><span style="color:#24292E">    .</span><span style="color:#6F42C1">pipe</span><span style="color:#24292E">(</span><span style="color:#6F42C1">uglify</span><span style="color:#24292E">())</span></span><span class="line"><span style="color:#24292E">    .</span><span style="color:#6F42C1">pipe</span><span style="color:#24292E">(</span><span style="color:#6F42C1">rename</span><span style="color:#24292E">(&#123; suffix: </span><span style="color:#032F62">".min"</span><span style="color:#24292E"> &#125;))</span></span><span class="line"><span style="color:#24292E">    .</span><span style="color:#6F42C1">pipe</span><span style="color:#24292E">(gulp.</span><span style="color:#6F42C1">dest</span><span style="color:#24292E">(</span><span style="color:#032F62">"."</span><span style="color:#24292E">));</span></span><span class="line"><span style="color:#24292E">&#125;);</span></span><span class="line"></span><span class="line"><span style="color:#24292E">gulp.</span><span style="color:#6F42C1">task</span><span style="color:#24292E">(</span><span style="color:#032F62">"default"</span><span style="color:#24292E">, gulp.</span><span style="color:#6F42C1">parallel</span><span style="color:#24292E">(</span><span style="color:#032F62">"js"</span><span style="color:#24292E">));</span></span></code></pre><h1 id="总结">总结</h1><p>至此这么一小段 js 终于被编译成可以运行的 minify 的 ES5 了。可是这么一通操作下来引入了 core-js 做 polyfill 体积不减反增<s>maxify</s>。</p><pre class="shiki github-light" style="background-color:#fff;color:#24292e" tabindex="0"><code class="language-text"><span class="line"><span>4.0K    main.js</span></span><span class="line"><span>36K     main.min.js</span></span></code></pre><p>所以说如果不考虑兼容性就直接用<code>gulp-uglify-es</code>好了。</p><p>前端的构建工具大版本总是不兼容，网上的信息也很多已经过时了。这篇文章估计在不久之后也会过时的。不得不说跟上时代最好的方法还是官方文档呀。</p><p><a href="https://github.com/fengkx/gulp-uglify-babel-demo">实例放在了 GitHub</a>，各个步骤都对应的分支。<br>我们还可以借助<code>github-history</code>来看 <a href="https://github-history.netlify.com/fengkx/gulp-uglify-babel-demo/blob/master/gulpfile.js">gulpfile 的变化</a>。</p>]]></content><summary type="html">&lt;p&gt;在写&lt;a href=&quot;https://github.com/fengkx/hexo-theme-purer&quot;&gt;Purer&lt;/a&gt;主题时用的 Gulp，在 minify JavaScript 的时候遇到了问题。花了挺长时间折腾才找到了解决方法。马后炮的说其实并不难，但是因为 </summary><category term="JavaScript" scheme="https://www.fengkx.top/tags/JavaScript/"/><category term="Gulp" scheme="https://www.fengkx.top/tags/Gulp/"/></entry><entry><title>缓存替换算法 LRU LFU</title><link href="https://www.fengkx.top/post/lru-lfu-cache/"/><id>https://www.fengkx.top/post/lru-lfu-cache/</id><published>2020-02-22T00:53:15.000Z</published><updated>2026-03-01T16:09:07.664Z</updated><content type="html"><![CDATA[<h1 id="缓存替换算法">缓存替换算法</h1><p>缓存替换算法决定了在有限的容量的缓存中，有新的项进入的时候选择哪一项被剔除的问题。<br><a href="https://en.wikipedia.org/wiki/Cache_replacement_policies">缓存算法有很多</a></p><p>假如我们以<strong>K-V</strong>形式的缓存为例。通常 KV 形式的缓存为了有<code>O(1)</code>的读取速度都会维护一个<code>HashMap</code><br>那么不同的算法的其中一个不同之处就是保存替换时决策所需要的信息而产生的额外的数据结构了。</p><h1 id="LRU">LRU</h1><p>LRU(Least recently used) 最近最少使用，实际上就是缓存中最久未被使用的项会被优先剔除。</p><p>它的思想是如果一个项很久都没被使用了，那么它应该被剔除缓存。</p><p>但是当缓存的访问是循环的顺序遍历，而且缓存容量不足以容纳遍历的项时，缓存项就会频繁进出影响效率。</p><h2 id="实现">实现</h2><p><a href="https://leetcode.com/problems/lru-cache/">Leetcode 上有 LRU Cache 的题目</a></p><p>为了知道最近最少访问的信息通常 LRU 的实现会采用双链表保存缓存项。因为缓存的数量会动态变化，所以会选择链表。而且会有不少的删除节点操作，为了达到<code>O(1)</code>的删除操作所以会用双链表。</p><pre class="shiki github-light" style="background-color:#fff;color:#24292e" tabindex="0"><code class="language-go"><span class="line"><span style="color:#D73A49">type</span><span style="color:#6F42C1"> CacheNode</span><span style="color:#D73A49"> struct</span><span style="color:#24292E"> &#123;</span></span><span class="line"><span style="color:#24292E">key   </span><span style="color:#D73A49">int</span></span><span class="line"><span style="color:#24292E">value </span><span style="color:#D73A49">int</span></span><span class="line"><span style="color:#24292E">prev  </span><span style="color:#D73A49">*</span><span style="color:#6F42C1">CacheNode</span></span><span class="line"><span style="color:#24292E">next  </span><span style="color:#D73A49">*</span><span style="color:#6F42C1">CacheNode</span></span><span class="line"><span style="color:#24292E">&#125;</span></span><span class="line"></span><span class="line"><span style="color:#D73A49">type</span><span style="color:#6F42C1"> LRUCache</span><span style="color:#D73A49"> struct</span><span style="color:#24292E"> &#123;</span></span><span class="line"><span style="color:#24292E">size     </span><span style="color:#D73A49">int</span></span><span class="line"><span style="color:#24292E">capacity </span><span style="color:#D73A49">int</span></span><span class="line"><span style="color:#24292E">head     </span><span style="color:#D73A49">*</span><span style="color:#6F42C1">CacheNode</span></span><span class="line"><span style="color:#24292E">tail     </span><span style="color:#D73A49">*</span><span style="color:#6F42C1">CacheNode</span></span><span class="line"><span style="color:#24292E">cache    </span><span style="color:#D73A49">map</span><span style="color:#24292E">[</span><span style="color:#D73A49">int</span><span style="color:#24292E">]</span><span style="color:#D73A49">*</span><span style="color:#6F42C1">CacheNode</span></span><span class="line"><span style="color:#24292E">&#125;</span></span></code></pre><p>我们保存链表的尾指针，每当有缓存项的访问或者新项都将对应的节点移到链表的头。头插入和删除节点的操作都是<code>O(1)</code></p><p><a href="https://github.com/fengkx/leetcode/blob/master/lru_cache/lru_cache.go">完整实现</a></p><h2 id="hashlru">hashlru</h2><p>还有一种近似的 LRU 算法<a href="https://github.com/dominictarr/hashlru#algorithm">HashLRU</a>通过两个<code>HashMap</code>的交换实现<code>O(1)</code>的复杂度，在实际的<a href="https://github.com/dominictarr/bench-lru">Benchmark</a>中的表现也很好。</p><h1 id="LFU">LFU</h1><p>LFU(Least Frequently Used) 最近最不频繁使用，缓存中被使用的次数最少的项会被优先剔除。它认为被用的越少的项越应该移除。</p><p>LFU 克服了 LRU 在大规模遍历时的缺点。但是容易导致旧数据的积累。同时新数据因为使用次数少容易反复被移出缓存导致长期无法积累跟大的使用次数。</p><h2 id="实现-2">实现</h2><p><a href="https://leetcode.com/problems/lfu-cache/">Leetcode 上有 LFU Cache 的题目</a><br>为了维护相关的访问频次信息需要额外的数据结构。一种各操作 (增删查) 都为<code>O(1)</code>的形式是使用两个双链表。其中一个保存访问频次，另一个用于保存某频次对应的节点。因为频次链表会经常有增删操作所以使用双链表。而保存某频次对应节点的链表也可以使用 HashMap 或者动态表。</p><pre class="shiki github-light" style="background-color:#fff;color:#24292e" tabindex="0"><code class="language-go"><span class="line"><span style="color:#D73A49">type</span><span style="color:#6F42C1"> CacheNode</span><span style="color:#D73A49"> struct</span><span style="color:#24292E"> &#123;</span></span><span class="line"><span style="color:#24292E">key    </span><span style="color:#D73A49">int</span></span><span class="line"><span style="color:#24292E">value  </span><span style="color:#D73A49">int</span></span><span class="line"><span style="color:#24292E">prev   </span><span style="color:#D73A49">*</span><span style="color:#6F42C1">CacheNode</span></span><span class="line"><span style="color:#24292E">next   </span><span style="color:#D73A49">*</span><span style="color:#6F42C1">CacheNode</span></span><span class="line"><span style="color:#24292E">parent </span><span style="color:#D73A49">*</span><span style="color:#6F42C1">FreqNode</span></span><span class="line"><span style="color:#24292E">&#125;</span></span><span class="line"></span><span class="line"><span style="color:#D73A49">type</span><span style="color:#6F42C1"> FreqNode</span><span style="color:#D73A49"> struct</span><span style="color:#24292E"> &#123;</span></span><span class="line"><span style="color:#24292E">freq </span><span style="color:#D73A49">int</span></span><span class="line"><span style="color:#24292E">head </span><span style="color:#D73A49">*</span><span style="color:#6F42C1">CacheNode</span></span><span class="line"><span style="color:#24292E">tail </span><span style="color:#D73A49">*</span><span style="color:#6F42C1">CacheNode</span></span><span class="line"><span style="color:#24292E">prev </span><span style="color:#D73A49">*</span><span style="color:#6F42C1">FreqNode</span></span><span class="line"><span style="color:#24292E">next </span><span style="color:#D73A49">*</span><span style="color:#6F42C1">FreqNode</span></span><span class="line"><span style="color:#24292E">&#125;</span></span><span class="line"></span><span class="line"><span style="color:#D73A49">type</span><span style="color:#6F42C1"> LFUCache</span><span style="color:#D73A49"> struct</span><span style="color:#24292E"> &#123;</span></span><span class="line"><span style="color:#24292E">size </span><span style="color:#D73A49">int</span></span><span class="line"><span style="color:#24292E">capacity </span><span style="color:#D73A49">int</span></span><span class="line"><span style="color:#24292E">cache </span><span style="color:#D73A49">map</span><span style="color:#24292E">[</span><span style="color:#D73A49">int</span><span style="color:#24292E">]</span><span style="color:#D73A49">*</span><span style="color:#6F42C1">CacheNode</span></span><span class="line"><span style="color:#24292E">lfuHead </span><span style="color:#D73A49">*</span><span style="color:#6F42C1">FreqNode</span></span><span class="line"><span style="color:#24292E">&#125;</span></span></code></pre><p><img loading="lazy" src="https://vip2.loli.io/2023/03/08/gI31FS8z5jx2CWp.png" alt="lfu"></p><p>缓存保存一个使用次数为 0 的频次节点的指针。新项加入时，头插入到次数为 1 的频次节点，时间复杂度为<code>O(1)</code>。</p><p>当某一个频次的节点 (除 0 以外) 所持有的缓存项个数为 0 时，该频次节点就会被移除。这样保证了 0 频次节点的下一个节点总是缓存中使用次数最少的。</p><p>此时，只要在频次节点中维护缓存项链表的尾指针，就知道了缓存中使用此是最少最老旧的缓存项。此番操作 (lfuHead.next.head) 的时间复杂度也是<code>O(1)</code></p><p><a href="https://github.com/fengkx/leetcode/blob/master/lfu_cache/lfu_cache.go">完整实现</a></p><h1 id="ARC">ARC</h1><p>ARC(Adaptive replacement cache) 同时使用 LRU 和 LFU。通过动态调整 LRU，LFU 的容量达到自适应的效果。<br>缓存中除了 LRU 和 LFU 所需的数据结构外，额外增加了两条链表 LRU 和 LFU 各一个称为 Ghost List。<br>开始时缓存容量被平分 LRU 和 LFU 两部分。当新项加入时，先加入到 LRU 中。当缓存项被访问而 LFU 中没有时，加入到 LFU 中。<br>如果缓存项被移除则将它的 Key 加入到对应的 Ghost List 中。每当缓存不命中时，都会检查 Ghost List。假如 Ghost List 中含有当前不命中的 Key 则该 Ghost List 对应的缓存的容量应该增加。</p><h1 id="参考文章">参考文章</h1><ul><li><a href="https://en.wikipedia.org/wiki/Cache_replacement_policies">Cache Replacement Policy Wiki</a></li><li><a href="https://github.com/dominictarr/hashlru#algorithm">LFU Cache Wiki</a></li><li><a href="http://dhruvbird.com/lfu.pdf">An O(1) algorithm for implementing the LFU cache eviction scheme</a></li><li><a href="https://github.com/dominictarr/hashlru#algorithm">Hash LRU</a></li><li>Leetcode discuss</li></ul>]]></content><summary type="html">&lt;h1 id=&quot;缓存替换算法&quot;&gt;缓存替换算法&lt;/h1&gt;
&lt;p&gt;缓存替换算法决定了在有限的容量的缓存中，有新的项进入的时候选择哪一项被剔除的问题。&lt;br&gt;
&lt;a href=&quot;https://en.wikipedia.org/wiki/Cache_replacement_polici</summary><category term="Cache" scheme="https://www.fengkx.top/tags/Cache/"/><category term="算法" scheme="https://www.fengkx.top/tags/%E7%AE%97%E6%B3%95/"/></entry><entry><title>Hexo-native-lazy-load 插件</title><link href="https://www.fengkx.top/post/hexo-native-lazy-load/"/><id>https://www.fengkx.top/post/hexo-native-lazy-load/</id><published>2019-12-01T12:54:45.000Z</published><updated>2026-03-01T16:09:07.664Z</updated><content type="html"><![CDATA[<h2 id="Chrome-原生支持图片-lazy-load">Chrome 原生支持图片 lazy load</h2><p>前段时间刷推的时候看到了<a href="https://web.dev/native-lazy-loading/">Chrome 开始原生支持 lazy load 的新闻</a>。简单的说就是 Chrome76 开始支持一个 loading 属性。有以下三种取值。</p><pre class="shiki github-light" style="background-color:#fff;color:#24292e" tabindex="0"><code class="language-text"><span class="line"><span>auto: Default lazy-loading behavior of the browser, which is the same as not including the attribute.</span></span><span class="line"><span>lazy: Defer loading of the resource until it reaches a calculated distance from the viewport.</span></span><span class="line"><span>eager: Load the resource immediately, regardless of where it's located on the page.</span></span></code></pre><p>想到可以把它放在博客里面作为一种额外的优化。(本博客已经上了大杀器 service worker 其实大部分时候图片都是从 service worker 下载的)</p><p>花了点时间写了一个 Hexo 插件<a href="https://www.npmjs.com/package/hexo-native-lazy-load">hexo-native-lazy-load</a>。</p><h2 id="Hexo-native-lazy-load">Hexo native lazy load</h2><p>这个插件要做的事情就是在 img 标签插入<code>loading=lazy</code>的属性。它使用了 Hexo 的<a href="https://hexo.io/api/filter">filter API</a></p><p>第一次写 Hexo 的插件。Hexo 的文档给我的感觉很不清楚（看不懂）。很多时候数据结构都要试。有个点一开始不知道。</p><p>Hexo 插件的入口文件有全局的<code>hexo</code>对象，对象下有<code>hexo.log</code>等价于<code>hexo-log</code>这个 package 不需要额外引入。</p><p>经过了一段时间的迭代了，下面说说具体功能</p><h3 id="optional-fallback">optional fallback</h3><p>在<code>hexo.config.lazy_load.fallback !== false</code>条件成立时，会给额外加上一个使用<a href="https://github.com/aFarkas/lazysizes">lazysizes</a>的 fallback lazy load。像这样</p><pre class="shiki github-light" style="background-color:#fff;color:#24292e" tabindex="0"><code class="language-js"><span class="line"><span style="color:#D73A49">if</span><span style="color:#24292E"> (</span><span style="color:#D73A49">!</span><span style="color:#24292E">(</span><span style="color:#032F62">"loading"</span><span style="color:#D73A49"> in</span><span style="color:#005CC5"> HTMLImageElement</span><span style="color:#24292E">.</span><span style="color:#005CC5">prototype</span><span style="color:#24292E">)) &#123;</span></span><span class="line"><span style="color:#D73A49">  const</span><span style="color:#005CC5"> srp</span><span style="color:#D73A49"> =</span><span style="color:#24292E"> document.</span><span style="color:#6F42C1">createElement</span><span style="color:#24292E">(</span><span style="color:#032F62">"script"</span><span style="color:#24292E">);</span></span><span class="line"><span style="color:#24292E">  srp.src </span><span style="color:#D73A49">=</span><span style="color:#032F62"> "https://cdn.jsdelivr.net/npm/lazysizes@5.1.1/lazysizes.min.js"</span><span style="color:#24292E">;</span></span><span class="line"><span style="color:#24292E">  document.body.</span><span style="color:#6F42C1">append</span><span style="color:#24292E">(srp);</span></span><span class="line"><span style="color:#D73A49">  const</span><span style="color:#005CC5"> imgs</span><span style="color:#D73A49"> =</span><span style="color:#24292E"> document.</span><span style="color:#6F42C1">querySelectorAll</span><span style="color:#24292E">(</span><span style="color:#032F62">"img"</span><span style="color:#24292E">);</span></span><span class="line"><span style="color:#24292E">  imgs.</span><span style="color:#6F42C1">forEach</span><span style="color:#24292E">((</span><span style="color:#E36209">el</span><span style="color:#24292E">) </span><span style="color:#D73A49">=></span><span style="color:#24292E"> &#123;</span></span><span class="line"><span style="color:#24292E">    el.</span><span style="color:#6F42C1">setAttribute</span><span style="color:#24292E">(</span><span style="color:#032F62">"data-src"</span><span style="color:#24292E">, el.</span><span style="color:#6F42C1">getAttribute</span><span style="color:#24292E">(</span><span style="color:#032F62">"src"</span><span style="color:#24292E">));</span></span><span class="line"><span style="color:#24292E">    el.</span><span style="color:#6F42C1">removeAttribute</span><span style="color:#24292E">(</span><span style="color:#032F62">"src"</span><span style="color:#24292E">);</span></span><span class="line"><span style="color:#24292E">    el.classList.</span><span style="color:#6F42C1">add</span><span style="color:#24292E">(</span><span style="color:#032F62">"lazyload"</span><span style="color:#24292E">);</span></span><span class="line"><span style="color:#24292E">  &#125;);</span></span><span class="line"><span style="color:#24292E">&#125;</span></span></code></pre><p>结合对象检测如果不支持 loading 属性则插入 fallback。</p><h3 id="添加本地图片的宽高属性">添加本地图片的宽高属性</h3><p>lazy load 需要尽量减少页面的 reflow。可以通过设置固定的宽高实现。我自己的博客的图片都是放在<code>post_asset_folder</code>下面。那么就可以通过本地读取文件的宽高直接添加到 img 元素的属性里。</p><p>插件提供了<code>width_height</code>设置项。当<code>hexo.config.post_asset_folder === true &amp;&amp; hexo.config.lazy_load.width_height !== false</code>条件成立时将会就会为本地的图片添加宽高。</p><h3 id="最终的效果">最终的效果</h3><p>支持 loading 属性的浏览器。</p><p><img loading="lazy" src="https://vip2.loli.io/2023/03/08/c5OxonrmSEpD1Jq.png" alt="loading-lazy"></p><p>不支持 loading 属性的浏览器</p><p><img loading="lazy" src="https://vip2.loli.io/2023/03/08/oN5meA1hnl3c6Ld.png" alt="lazysizes"></p><p>传送门</p><p><a href="https://nodei.co/npm/hexo-native-lazy-load/"><img loading="lazy" src="https://nodei.co/npm/hexo-native-lazy-load.png?stars=true" alt="NPM"></a></p>]]></content><summary type="html">&lt;h2 id=&quot;Chrome-原生支持图片-lazy-load&quot;&gt;Chrome 原生支持图片 lazy load&lt;/h2&gt;
&lt;p&gt;前段时间刷推的时候看到了&lt;a href=&quot;https://web.dev/native-lazy-loading/&quot;&gt;Chrome 开始原生支持 la</summary><category term="Hexo" scheme="https://www.fengkx.top/tags/Hexo/"/><category term="lazyload" scheme="https://www.fengkx.top/tags/lazyload/"/></entry><entry><title>SM4 对称加密算法与其 CBC 模式实现</title><link href="https://www.fengkx.top/post/sm4-implementing/"/><id>https://www.fengkx.top/post/sm4-implementing/</id><published>2019-11-30T14:10:45.000Z</published><updated>2026-03-01T16:09:07.668Z</updated><content type="html"><![CDATA[<p>继<a href="https://www.fengkx.top/post/sm3-implementing/">SM3</a>之后下一个要实现的算法是<a href="http://www.sca.gov.cn/sca/xwdt/2012-03/21/content_1002392.shtml">SM4</a>。</p><h1 id="SM4-对称分组加密">SM4 对称分组加密</h1><p>SM4 算法是一个对称分组加密算法，类似于 DES 或者 AES。对称加密也就是加密与解密的密钥是同一个。加密和解密时会将输入数据按固定的比特长度分块进行加密。不同于 AES 提供多个版本。SM4 的的分组长度为 128bit，密钥长度为 128bit。SM4 的一个有趣的地方在于，生成密钥的算法和加密算法本身是一致的。分组密码实际使用时，对于消息长度不是分组倍数长度的消息要进行 padding，以此也产生出了很多模式。</p><p>上一次<a href="https://github.com/fengkx/sm3">实现了 SM3 算法</a>之后看了一下 OpenSSL 的实现，觉得自己有必要学习一下 OpenSSL 的函数接口组织。所以这一次先去看了看 OpenSSL 的实现并模仿了它的接口的设计，自己实现了<a href="https://github.com/fengkx/sm4">SM4 算法</a>。</p><h1 id="具体实现">具体实现</h1><pre class="shiki github-light" style="background-color:#fff;color:#24292e" tabindex="0"><code class="language-c"><span class="line"><span style="color:#D73A49">#ifndef</span><span style="color:#6F42C1"> _sm4_H_</span></span><span class="line"><span style="color:#D73A49">#define</span><span style="color:#6F42C1"> _sm4_H_</span></span><span class="line"></span><span class="line"><span style="color:#D73A49">#include</span><span style="color:#032F62"> &#x3C;stdio.h></span></span><span class="line"><span style="color:#D73A49">#include</span><span style="color:#032F62"> &#x3C;stdint.h></span></span><span class="line"></span><span class="line"><span style="color:#D73A49">#define</span><span style="color:#6F42C1"> SM4_DECRYPT</span><span style="color:#005CC5"> 0</span></span><span class="line"><span style="color:#D73A49">#define</span><span style="color:#6F42C1"> SM4_ENCRYPT</span><span style="color:#005CC5"> 1</span></span><span class="line"></span><span class="line"><span style="color:#D73A49">#define</span><span style="color:#6F42C1"> SM4_BLOCK_SIZE</span><span style="color:#005CC5">    16</span></span><span class="line"><span style="color:#D73A49">#define</span><span style="color:#6F42C1"> SM4_KEY_SCHEDULE</span><span style="color:#005CC5">  32</span></span><span class="line"></span><span class="line"><span style="color:#D73A49">typedef</span><span style="color:#D73A49"> struct</span><span style="color:#24292E"> &#123;</span></span><span class="line"><span style="color:#D73A49">    int</span><span style="color:#24292E"> mode;</span><span style="color:#6A737D"> // ENCRYPT OR DECRYPT</span></span><span class="line"><span style="color:#D73A49">    uint32_t</span><span style="color:#E36209"> rk</span><span style="color:#24292E">[SM4_KEY_SCHEDULE];</span><span style="color:#6A737D"> // rotkey</span></span><span class="line"></span><span class="line"><span style="color:#24292E">&#125;</span></span><span class="line"><span style="color:#24292E">sm4_ctx;</span></span><span class="line"><span style="color:#6A737D">    // 对外的接口</span></span><span class="line"><span style="color:#D73A49">    int</span><span style="color:#6F42C1"> sm4_set_key</span><span style="color:#24292E">(</span><span style="color:#D73A49">const</span><span style="color:#D73A49"> uint8_t</span><span style="color:#D73A49"> *</span><span style="color:#E36209">key</span><span style="color:#24292E">, sm4_ctx </span><span style="color:#D73A49">*</span><span style="color:#D73A49"> const</span><span style="color:#E36209"> ctx</span><span style="color:#24292E">);</span><span style="color:#6A737D"> // key 128 bit len 16</span></span><span class="line"><span style="color:#D73A49">    void</span><span style="color:#6F42C1"> sm4_encrypt</span><span style="color:#24292E">(</span><span style="color:#D73A49">const</span><span style="color:#D73A49"> uint8_t</span><span style="color:#D73A49"> *</span><span style="color:#E36209">in</span><span style="color:#24292E">, </span><span style="color:#D73A49">uint8_t</span><span style="color:#D73A49"> *</span><span style="color:#E36209">out</span><span style="color:#24292E">, </span><span style="color:#D73A49">const</span><span style="color:#24292E"> sm4_ctx </span><span style="color:#D73A49">*</span><span style="color:#E36209">ctx</span><span style="color:#24292E">);</span></span><span class="line"><span style="color:#D73A49">    void</span><span style="color:#6F42C1"> sm4_decrypt</span><span style="color:#24292E">(</span><span style="color:#D73A49">const</span><span style="color:#D73A49"> uint8_t</span><span style="color:#D73A49"> *</span><span style="color:#E36209">in</span><span style="color:#24292E">, </span><span style="color:#D73A49">uint8_t</span><span style="color:#D73A49"> *</span><span style="color:#E36209">out</span><span style="color:#24292E">, </span><span style="color:#D73A49">const</span><span style="color:#24292E"> sm4_ctx </span><span style="color:#D73A49">*</span><span style="color:#E36209">ctx</span><span style="color:#24292E">);</span></span><span class="line"></span><span class="line"><span style="color:#6A737D">    // Util 类函数</span></span><span class="line"><span style="color:#D73A49">    static</span><span style="color:#D73A49"> inline</span><span style="color:#D73A49"> uint32_t</span><span style="color:#6F42C1"> load_uint32_be</span><span style="color:#24292E">(</span><span style="color:#D73A49">const</span><span style="color:#D73A49"> uint8_t</span><span style="color:#D73A49"> *</span><span style="color:#E36209">b</span><span style="color:#24292E">, </span><span style="color:#D73A49">int</span><span style="color:#E36209"> n</span><span style="color:#24292E">);</span></span><span class="line"><span style="color:#D73A49">    static</span><span style="color:#D73A49"> inline</span><span style="color:#D73A49"> void</span><span style="color:#6F42C1"> store_uint32_be</span><span style="color:#24292E">(</span><span style="color:#D73A49">uint32_t</span><span style="color:#E36209"> v</span><span style="color:#24292E">, </span><span style="color:#D73A49">uint8_t</span><span style="color:#D73A49"> *</span><span style="color:#D73A49"> const</span><span style="color:#E36209"> b</span><span style="color:#24292E">);</span></span><span class="line"><span style="color:#D73A49">    static</span><span style="color:#D73A49"> inline</span><span style="color:#D73A49"> void</span><span style="color:#6F42C1"> xor_blk</span><span style="color:#24292E">(</span><span style="color:#D73A49">uint8_t</span><span style="color:#D73A49"> *</span><span style="color:#E36209">a</span><span style="color:#24292E">, </span><span style="color:#D73A49">uint8_t</span><span style="color:#D73A49"> *</span><span style="color:#E36209">b</span><span style="color:#24292E">);</span></span><span class="line"></span><span class="line"><span style="color:#6A737D">    // SM4 主要的置换函数</span></span><span class="line"><span style="color:#D73A49">    static</span><span style="color:#D73A49"> inline</span><span style="color:#D73A49"> void</span><span style="color:#6F42C1"> SM4_F</span><span style="color:#24292E">(</span><span style="color:#D73A49">uint32_t</span><span style="color:#D73A49"> *</span><span style="color:#D73A49"> const</span><span style="color:#E36209"> blks</span><span style="color:#24292E">, </span><span style="color:#D73A49">const</span><span style="color:#D73A49"> uint32_t</span><span style="color:#D73A49"> *</span><span style="color:#E36209">rkg</span><span style="color:#24292E">);</span><span style="color:#6A737D"> // blks len should be 4 as 128bit</span></span><span class="line"><span style="color:#D73A49">    static</span><span style="color:#D73A49"> inline</span><span style="color:#D73A49"> uint32_t</span><span style="color:#6F42C1"> SM4_T</span><span style="color:#24292E">(</span><span style="color:#D73A49">uint32_t</span><span style="color:#E36209"> X</span><span style="color:#24292E">);</span></span><span class="line"></span><span class="line"><span style="color:#6A737D">    // 对外文件加密接口</span></span><span class="line"><span style="color:#D73A49">    void</span><span style="color:#6F42C1"> sm4_encrypt_file</span><span style="color:#24292E">(FILE </span><span style="color:#D73A49">*</span><span style="color:#E36209">in</span><span style="color:#24292E">, FILE </span><span style="color:#D73A49">*</span><span style="color:#E36209">out</span><span style="color:#24292E">, sm4_ctx </span><span style="color:#D73A49">*</span><span style="color:#E36209">ctx</span><span style="color:#24292E">);</span></span><span class="line"><span style="color:#D73A49">    void</span><span style="color:#6F42C1"> sm4_decrypt_file</span><span style="color:#24292E">(FILE </span><span style="color:#D73A49">*</span><span style="color:#E36209">in</span><span style="color:#24292E">, FILE </span><span style="color:#D73A49">*</span><span style="color:#E36209">out</span><span style="color:#24292E">, sm4_ctx </span><span style="color:#D73A49">*</span><span style="color:#E36209">ctx</span><span style="color:#24292E">);</span></span><span class="line"><span style="color:#D73A49">    void</span><span style="color:#6F42C1"> sm4_cbc_encrypt_file</span><span style="color:#24292E">(FILE </span><span style="color:#D73A49">*</span><span style="color:#E36209">in</span><span style="color:#24292E">, FILE </span><span style="color:#D73A49">*</span><span style="color:#E36209">out</span><span style="color:#24292E">, sm4_ctx </span><span style="color:#D73A49">*</span><span style="color:#E36209">ctx</span><span style="color:#24292E">);</span></span><span class="line"><span style="color:#D73A49">    void</span><span style="color:#6F42C1"> sm4_cbc_decrypt_file</span><span style="color:#24292E">(FILE </span><span style="color:#D73A49">*</span><span style="color:#E36209">in</span><span style="color:#24292E">, FILE </span><span style="color:#D73A49">*</span><span style="color:#E36209">out</span><span style="color:#24292E">, sm4_ctx </span><span style="color:#D73A49">*</span><span style="color:#E36209">ctx</span><span style="color:#24292E">);</span></span><span class="line"></span><span class="line"><span style="color:#D73A49">#endif</span><span style="color:#6A737D"> // _sm4_H_</span></span></code></pre><p>略过几个 util 类的函数，下面来分析一下算法的一些步骤。<br>SM4 的操作对象为 128bit 的分块，其中的运算都将 128bit 分为 32bit 的字进行。每个块 4 个字。</p><h2 id="生成轮秘钥">生成轮秘钥</h2><p>此类算法一般都会先生成轮秘钥，SM4 也不例外。SM4 生成轮秘钥的算法和加密算法是几乎一致的，只是中间的一个变换有点区别。按文档实现就好。</p><p>其中 SBox 的置换操作对于一个字来说是有四个 SBox 并排置换实现的</p><pre class="shiki github-light" style="background-color:#fff;color:#24292e" tabindex="0"><code class="language-c"><span class="line"><span style="color:#24292E">    t </span><span style="color:#D73A49">|=</span><span style="color:#24292E"> (</span><span style="color:#D73A49">uint32_t</span><span style="color:#24292E">)(</span><span style="color:#E36209">SM4_S</span><span style="color:#24292E">[(</span><span style="color:#D73A49">uint8_t</span><span style="color:#24292E">) (X</span><span style="color:#D73A49">>></span><span style="color:#005CC5">24</span><span style="color:#24292E">)]) </span><span style="color:#D73A49">&#x3C;&#x3C;</span><span style="color:#005CC5"> 24</span><span style="color:#24292E">;</span></span><span class="line"><span style="color:#24292E">    t </span><span style="color:#D73A49">|=</span><span style="color:#24292E"> (</span><span style="color:#D73A49">uint32_t</span><span style="color:#24292E">)(</span><span style="color:#E36209">SM4_S</span><span style="color:#24292E">[(</span><span style="color:#D73A49">uint8_t</span><span style="color:#24292E">) (X</span><span style="color:#D73A49">>></span><span style="color:#005CC5">16</span><span style="color:#24292E">)]) </span><span style="color:#D73A49">&#x3C;&#x3C;</span><span style="color:#005CC5"> 16</span><span style="color:#24292E">;</span></span><span class="line"><span style="color:#24292E">    t </span><span style="color:#D73A49">|=</span><span style="color:#24292E"> (</span><span style="color:#D73A49">uint32_t</span><span style="color:#24292E">)(</span><span style="color:#E36209">SM4_S</span><span style="color:#24292E">[(</span><span style="color:#D73A49">uint8_t</span><span style="color:#24292E">) (X</span><span style="color:#D73A49">>></span><span style="color:#005CC5">8</span><span style="color:#24292E">)]) </span><span style="color:#D73A49">&#x3C;&#x3C;</span><span style="color:#005CC5"> 8</span><span style="color:#24292E">;</span></span><span class="line"><span style="color:#24292E">    t </span><span style="color:#D73A49">|=</span><span style="color:#24292E"> (</span><span style="color:#D73A49">uint32_t</span><span style="color:#24292E">)</span><span style="color:#E36209">SM4_S</span><span style="color:#24292E">[(</span><span style="color:#D73A49">uint8_t</span><span style="color:#24292E">) X];</span></span><span class="line"></span><span class="line"><span style="color:#24292E">        t </span><span style="color:#D73A49">=</span><span style="color:#24292E"> t </span><span style="color:#D73A49">^</span><span style="color:#6F42C1"> ROT32L</span><span style="color:#24292E">(t, </span><span style="color:#005CC5">13</span><span style="color:#24292E">) </span><span style="color:#D73A49">^</span><span style="color:#6F42C1"> ROT32L</span><span style="color:#24292E">(t, </span><span style="color:#005CC5">23</span><span style="color:#24292E">);</span></span></code></pre><p>如果不是提前读了 OpenSSL 的源码，我这里可能会选择强转指针而不是移位加或运算。相对强转指针这样的算法可读性明显更好。</p><h2 id="使用轮秘钥进行加密">使用轮秘钥进行加密</h2><p>加密的只要算法就是 SBox 的置换和 L 变换，文档都有详细说明了。</p><pre class="shiki github-light" style="background-color:#fff;color:#24292e" tabindex="0"><code class="language-c"><span class="line"><span style="color:#D73A49">static</span><span style="color:#D73A49"> inline</span><span style="color:#D73A49"> uint32_t</span><span style="color:#6F42C1"> SM4_T</span><span style="color:#24292E">(</span><span style="color:#D73A49">uint32_t</span><span style="color:#E36209"> X</span><span style="color:#24292E">) &#123;</span></span><span class="line"><span style="color:#D73A49">uint32_t</span><span style="color:#24292E"> t </span><span style="color:#D73A49">=</span><span style="color:#005CC5"> 0</span><span style="color:#24292E">;</span></span><span class="line"><span style="color:#24292E">t </span><span style="color:#D73A49">|=</span><span style="color:#24292E"> ((</span><span style="color:#D73A49">uint32_t</span><span style="color:#24292E">)</span><span style="color:#E36209">SM4_S</span><span style="color:#24292E">[(</span><span style="color:#D73A49">uint8_t</span><span style="color:#24292E">)(X</span><span style="color:#D73A49">>></span><span style="color:#005CC5">24</span><span style="color:#24292E">)]) </span><span style="color:#D73A49">&#x3C;&#x3C;</span><span style="color:#005CC5"> 24</span><span style="color:#24292E">;</span></span><span class="line"><span style="color:#24292E">t </span><span style="color:#D73A49">|=</span><span style="color:#24292E"> ((</span><span style="color:#D73A49">uint32_t</span><span style="color:#24292E">)</span><span style="color:#E36209">SM4_S</span><span style="color:#24292E">[(</span><span style="color:#D73A49">uint8_t</span><span style="color:#24292E">)(X</span><span style="color:#D73A49">>></span><span style="color:#005CC5">16</span><span style="color:#24292E">)]) </span><span style="color:#D73A49">&#x3C;&#x3C;</span><span style="color:#005CC5"> 16</span><span style="color:#24292E">;</span></span><span class="line"><span style="color:#24292E">t </span><span style="color:#D73A49">|=</span><span style="color:#24292E"> ((</span><span style="color:#D73A49">uint32_t</span><span style="color:#24292E">)</span><span style="color:#E36209">SM4_S</span><span style="color:#24292E">[(</span><span style="color:#D73A49">uint8_t</span><span style="color:#24292E">)(X</span><span style="color:#D73A49">>></span><span style="color:#005CC5">8</span><span style="color:#24292E">)]) </span><span style="color:#D73A49">&#x3C;&#x3C;</span><span style="color:#005CC5"> 8</span><span style="color:#24292E">;</span></span><span class="line"><span style="color:#24292E">t </span><span style="color:#D73A49">|=</span><span style="color:#24292E"> ((</span><span style="color:#D73A49">uint32_t</span><span style="color:#24292E">)</span><span style="color:#E36209">SM4_S</span><span style="color:#24292E">[(</span><span style="color:#D73A49">uint8_t</span><span style="color:#24292E">)(X)]);</span></span><span class="line"><span style="color:#24292E">t </span><span style="color:#D73A49">^=</span><span style="color:#6F42C1"> ROT32L</span><span style="color:#24292E">(t, </span><span style="color:#005CC5">2</span><span style="color:#24292E">) </span><span style="color:#D73A49">^</span><span style="color:#6F42C1"> ROT32L</span><span style="color:#24292E">(t, </span><span style="color:#005CC5">10</span><span style="color:#24292E">) </span><span style="color:#D73A49">^</span><span style="color:#6F42C1"> ROT32L</span><span style="color:#24292E">(t, </span><span style="color:#005CC5">18</span><span style="color:#24292E">) </span><span style="color:#D73A49">^</span><span style="color:#6F42C1"> ROT32L</span><span style="color:#24292E">(t, </span><span style="color:#005CC5">24</span><span style="color:#24292E">);</span></span><span class="line"><span style="color:#D73A49">return</span><span style="color:#24292E"> t;</span></span><span class="line"></span><span class="line"><span style="color:#24292E">&#125;</span></span></code></pre><p>可以看到算法与轮秘钥生成算法是一样的，只是最后的 L 变换有所区别。</p><h2 id="解密算法">解密算法</h2><p>SM4 解密算法与加密算法一致，只是使用轮秘钥的时候倒序使用</p><h1 id="总结">总结</h1><p>算法实现不难。在如何提升速度方面 OpenSSL 给了一个很好的示范。它先把一个字 (32bit) 的 SBox 的置换和 L 置换的结果<a href="https://github.com/openssl/openssl/blob/master/crypto/sm4/sm4.c#L43-L86">提前算了出来</a>。</p><h1 id="实际应用-加密文件">实际应用 - 加密文件</h1><h2 id="Padding">Padding</h2><p>实际应用过程中的 message 不一定是 128bit 整数倍的，所以需要进行 padding。padding 要考虑的东西主要是要让 padding 能够被识别出来。由于输入是二进制流所以不能通过 Magic bytes 的形式区分。在网上找相关的资料可以查到不少<a href="http://www.demonk.cn/2018/07/02/aes-padding-native-java/">padding 的算法</a>。</p><p>在这里选用 PKCS#5 算法。该算法就是将不足 16byte 的块填充成 16byte，其中填充的每一块的数值都是 padding 的长度。一个特例是，当填充恰好为 16byte 的块的时候仍需填充额外的 16byte。</p><p>举个例子。</p><pre class="shiki github-light" style="background-color:#fff;color:#24292e" tabindex="0"><code class="language-text"><span class="line"><span>// 加入最后一块为</span></span><span class="line"><span>0x12 0x34 0x56</span></span><span class="line"><span>则填充长度为 16-3=13 0x0d</span></span><span class="line"><span>0x12 0x34 0x56 0x0d 0x0d 0x0d 0x0d 0x0d 0x0d 0x0d 0x0d 0x0d 0x0d 0x0d 0x0d 0x0d</span></span><span class="line"><span>填充恰好 16byte 的块时，仍填充 16bytes 的 0x10</span></span></code></pre><p>额外填充的目的是确保解密后可以准确将 padding 移除而没有歧义。</p><h2 id="CBC">CBC</h2><p>CBC 从英文全称上去理解（Cipher Block Chaining）这种模式下每一个分组的明文经过 cipher 加密之前还会和前一个分组加密后的密文异或。第一个分组则会和一个随机生成的 IV 进行异或。</p><p><img loading="lazy" src="https://vip2.loli.io/2023/03/08/svHPeIj52g7oYGt.png" alt="cbc_enc"></p><p>解密的时候则会将解密后的输出与前一个分组的密文或者 IV 进行异或得到明文。</p><p><img loading="lazy" src="https://vip2.loli.io/2023/03/08/JvS9nBRUo6mZEzG.png" alt="cbc_dec"></p><p>CBC 模式的优点在于对于重复的明文加密出来的块因为经过上一个块的异或得到的密文会不一样。这样输入与明文没有固定的关系。</p><h2 id="具体实现-2">具体实现</h2><p>首先我们生成的 IV 要是密码安全的 random bytes。Uinx 下应该读<code>/dev/urandom</code>可以做到，但是为了能够 Protable 用了一个库<a href="https://github.com/dsprenkels/randombytes/">randombytes</a></p><p>下面看实现代码，之前有同学问我大文件怎么读。我的想法是只要获取了文件的长度，然后用流的方式一次进一部分就可以了。读到最后一块的时候再来 padding。读取文件长度可以通过<code>fseek</code>+<code>ftell</code>来实现。<br>需要注意的是大文件（实测 4G 左右）的时候<a href="https://stackoverflow.com/questions/24399015/fseek-with-seek-end-returns-a-invalid-argument-error-to-manage-large-data7gb?tdsourcetag=s_pctim_aiomsg">MinGW 编译的 fseek 会出错</a>，我暂时找不到很好的解决方式。有大佬知道的话还请指点一下。</p><pre class="shiki github-light" style="background-color:#fff;color:#24292e" tabindex="0"><code class="language-c"><span class="line"><span style="color:#D73A49">void</span><span style="color:#6F42C1"> sm4_cbc_encrypt_file</span><span style="color:#24292E">(FILE </span><span style="color:#D73A49">*</span><span style="color:#E36209">in</span><span style="color:#24292E">, FILE </span><span style="color:#D73A49">*</span><span style="color:#E36209">out</span><span style="color:#24292E">, sm4_ctx </span><span style="color:#D73A49">*</span><span style="color:#E36209">ctx</span><span style="color:#24292E">) &#123;</span></span><span class="line"><span style="color:#D73A49">uint8_t</span><span style="color:#E36209"> iv</span><span style="color:#24292E">[SM4_BLOCK_SIZE];</span></span><span class="line"><span style="color:#D73A49">uint8_t</span><span style="color:#E36209"> buf</span><span style="color:#24292E">[SM4_BLOCK_SIZE], </span><span style="color:#E36209">out_buf</span><span style="color:#24292E">[SM4_BLOCK_SIZE];</span></span><span class="line"></span><span class="line"><span style="color:#D73A49">long</span><span style="color:#24292E"> sz, nremain, nread</span><span style="color:#D73A49">=</span><span style="color:#005CC5">0</span><span style="color:#24292E">, padding_byte_len;</span></span><span class="line"><span style="color:#6F42C1">fseek</span><span style="color:#24292E">(in, </span><span style="color:#005CC5">0</span><span style="color:#24292E">, SEEK_END);</span></span><span class="line"><span style="color:#24292E">sz </span><span style="color:#D73A49">=</span><span style="color:#6F42C1"> ftell</span><span style="color:#24292E">(in);</span></span><span class="line"><span style="color:#24292E">nremain </span><span style="color:#D73A49">=</span><span style="color:#24292E"> sz;</span></span><span class="line"><span style="color:#6F42C1">rewind</span><span style="color:#24292E">(in);</span></span><span class="line"></span><span class="line"><span style="color:#6A737D">    // 将 IV 至于加密输出的文件开头 16byte</span></span><span class="line"><span style="color:#6F42C1">randombytes</span><span style="color:#24292E">(iv, SM4_BLOCK_SIZE);</span></span><span class="line"><span style="color:#6F42C1">fwrite</span><span style="color:#24292E">(iv, </span><span style="color:#005CC5">1</span><span style="color:#24292E">, SM4_BLOCK_SIZE, out);</span></span><span class="line"></span><span class="line"><span style="color:#D73A49">int</span><span style="color:#24292E"> n</span><span style="color:#D73A49">=</span><span style="color:#005CC5">0</span><span style="color:#24292E">;</span></span><span class="line"><span style="color:#6F42C1">memcpy</span><span style="color:#24292E">(out_buf, iv, SM4_BLOCK_SIZE);</span></span><span class="line"><span style="color:#D73A49">while</span><span style="color:#24292E">(nremain </span><span style="color:#D73A49">>=</span><span style="color:#24292E"> SM4_BLOCK_SIZE) &#123;</span></span><span class="line"><span style="color:#24292E">n </span><span style="color:#D73A49">=</span><span style="color:#6F42C1"> fread</span><span style="color:#24292E">(buf, </span><span style="color:#005CC5">1</span><span style="color:#24292E">, SM4_BLOCK_SIZE, in);</span></span><span class="line"><span style="color:#24292E">nremain </span><span style="color:#D73A49">-=</span><span style="color:#24292E">n;</span></span><span class="line"><span style="color:#24292E">nread </span><span style="color:#D73A49">+=</span><span style="color:#24292E"> n;</span></span><span class="line"><span style="color:#6A737D">        // SM4 CBC 模式加密</span></span><span class="line"><span style="color:#6F42C1">xor_blk</span><span style="color:#24292E">(buf, out_buf);</span></span><span class="line"><span style="color:#6F42C1">sm4_encrypt</span><span style="color:#24292E">(buf, out_buf, ctx);</span></span><span class="line"><span style="color:#6F42C1">fwrite</span><span style="color:#24292E">(out_buf, </span><span style="color:#005CC5">1</span><span style="color:#24292E">, SM4_BLOCK_SIZE, out);</span></span><span class="line"><span style="color:#24292E">&#125;</span></span><span class="line"></span><span class="line"><span style="color:#24292E">padding_byte_len </span><span style="color:#D73A49">=</span><span style="color:#24292E"> SM4_BLOCK_SIZE </span><span style="color:#D73A49">-</span><span style="color:#24292E"> nremain;</span></span><span class="line"></span><span class="line"><span style="color:#6F42C1">memset</span><span style="color:#24292E">(buf, </span><span style="color:#005CC5">0</span><span style="color:#24292E">, SM4_BLOCK_SIZE);</span></span><span class="line"><span style="color:#24292E">n </span><span style="color:#D73A49">=</span><span style="color:#6F42C1"> fread</span><span style="color:#24292E">(buf, </span><span style="color:#005CC5">1</span><span style="color:#24292E">, nremain, in);</span></span><span class="line"><span style="color:#24292E">nremain </span><span style="color:#D73A49">-=</span><span style="color:#24292E"> n;</span></span><span class="line"><span style="color:#24292E">nread </span><span style="color:#D73A49">+=</span><span style="color:#24292E"> n;</span></span><span class="line"></span><span class="line"><span style="color:#6A737D">    // 最后一块 padding 并且加密输出</span></span><span class="line"><span style="color:#D73A49">if</span><span style="color:#24292E">(padding_byte_len </span><span style="color:#D73A49">!=</span><span style="color:#24292E"> SM4_BLOCK_SIZE) &#123;</span></span><span class="line"><span style="color:#6F42C1">fread</span><span style="color:#24292E">(buf, </span><span style="color:#005CC5">1</span><span style="color:#24292E">, nremain, in);</span></span><span class="line"><span style="color:#6F42C1">memset</span><span style="color:#24292E">(buf</span><span style="color:#D73A49">+</span><span style="color:#24292E">n, padding_byte_len, padding_byte_len);</span></span><span class="line"><span style="color:#6F42C1">xor_blk</span><span style="color:#24292E">(buf, out_buf);</span></span><span class="line"><span style="color:#6F42C1">sm4_encrypt</span><span style="color:#24292E">(buf, out_buf, ctx);</span></span><span class="line"><span style="color:#6F42C1">fwrite</span><span style="color:#24292E">(out_buf, </span><span style="color:#005CC5">1</span><span style="color:#24292E">, SM4_BLOCK_SIZE, out);</span></span><span class="line"><span style="color:#24292E">&#125; </span><span style="color:#D73A49">else</span><span style="color:#24292E"> &#123;</span></span><span class="line"><span style="color:#6F42C1">memset</span><span style="color:#24292E">(buf, SM4_BLOCK_SIZE, SM4_BLOCK_SIZE);</span></span><span class="line"><span style="color:#6F42C1">xor_blk</span><span style="color:#24292E">(buf, out_buf);</span></span><span class="line"><span style="color:#6F42C1">sm4_encrypt</span><span style="color:#24292E">(buf, out_buf, ctx);</span></span><span class="line"><span style="color:#6F42C1">fwrite</span><span style="color:#24292E">(out_buf, </span><span style="color:#005CC5">1</span><span style="color:#24292E">, SM4_BLOCK_SIZE, out);</span></span><span class="line"><span style="color:#24292E">&#125;</span></span><span class="line"></span><span class="line"><span style="color:#24292E">&#125;</span></span></code></pre><p>解密的操作类似，知道了文件头 16byte 为 IV，先读出来，然后依次读取并且解密。因为是经过 padding 的所以最后一块一定是有 pading，而且最后一 byte 一定是 padding 长度。然后输出非 padding 部分的明文即可</p><pre class="shiki github-light" style="background-color:#fff;color:#24292e" tabindex="0"><code class="language-c"><span class="line"><span style="color:#24292E">n </span><span style="color:#D73A49">=</span><span style="color:#6F42C1"> fread</span><span style="color:#24292E">(buf, </span><span style="color:#005CC5">1</span><span style="color:#24292E">, SM4_BLOCK_SIZE, in);</span></span><span class="line"><span style="color:#6F42C1">sm4_decrypt</span><span style="color:#24292E">(buf, out_buf, ctx);</span></span><span class="line"><span style="color:#6F42C1">xor_blk</span><span style="color:#24292E">(out_buf, iv);</span></span><span class="line"><span style="color:#D73A49">long</span><span style="color:#24292E"> padding_byte_len </span><span style="color:#D73A49">=</span><span style="color:#E36209"> out_buf</span><span style="color:#24292E">[SM4_BLOCK_SIZE</span><span style="color:#D73A49">-</span><span style="color:#005CC5">1</span><span style="color:#24292E">];</span></span><span class="line"><span style="color:#6F42C1">fwrite</span><span style="color:#24292E">(out_buf, </span><span style="color:#005CC5">1</span><span style="color:#24292E">, SM4_BLOCK_SIZE</span><span style="color:#D73A49">-</span><span style="color:#24292E">padding_byte_len, out);</span></span></code></pre><h1 id="体会">体会</h1><p>学了点对称分组密码的基本结构还有具体实现吧。OpenSSL 为我节省了不少功夫少走的不少弯路。O(∩_∩)O 哈哈~</p><h1 id="参考文章">参考文章</h1><ul><li><a href="https://github.com/fengkx/sm4/blob/master/SM4.pdf">SM4 文档</a></li><li><a href="https://neuqzxy.github.io/2017/06/15/%E6%AC%A3%E4%BB%94%E5%B8%A6%E4%BD%A0%E9%9B%B6%E5%9F%BA%E7%A1%80%E5%85%A5%E9%97%A8SM4%E5%8A%A0%E5%AF%86%E7%AE%97%E6%B3%95/">欣仔带你零基础入门 SM4 加密算法</a></li><li><a href="http://www.demonk.cn/2018/07/02/aes-padding-native-java/">AES 加密简介——填充算法</a></li><li><a href="https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#CBC">CBC wiki</a></li></ul>]]></content><summary type="html">&lt;p&gt;继&lt;a href=&quot;https://www.fengkx.top/post/sm3-implementing/&quot;&gt;SM3&lt;/a&gt;之后下一个要实现的算法是&lt;a href=&quot;http://www.sca.gov.cn/sca/xwdt/2012-03/21/content_10</summary><category term="Crypto" scheme="https://www.fengkx.top/tags/Crypto/"/><category term="SM4" scheme="https://www.fengkx.top/tags/SM4/"/><category term="对称加密" scheme="https://www.fengkx.top/tags/%E5%AF%B9%E7%A7%B0%E5%8A%A0%E5%AF%86/"/></entry><entry><title>SM3 哈希密码算法实现</title><link href="https://www.fengkx.top/post/sm3-implementing/"/><id>https://www.fengkx.top/post/sm3-implementing/</id><published>2019-11-06T23:10:45.000Z</published><updated>2026-03-01T16:09:07.668Z</updated><content type="html"><![CDATA[<p>这学期欧气爆棚，有幸在第一轮选到了密码学研学项目的非正式课程。第一个要学习的算法是<a href="http://www.sca.gov.cn/sca/xwdt/2010-12/17/content_1002389.shtml">SM3</a></p><h1 id="SM3-Hash">SM3 Hash</h1><p>SM3 是一个<a href="https://en.wikipedia.org/wiki/Cryptographic_hash_function">哈希算法</a>而不是加密。哈希是将一个任意长的输入映射到一个固定长的输出的函数。在 SM3 中输出为 256bit 长度。它的特点就是具有单向性，并且同样的输入总是映射到同样的输出。单向性从输入能轻松求出输出。但是却很难从输出反推输入。这样的性质让 Hash 有了它的应用。比如说网站后台的密码存储通常不会存储明文而是存储密码的哈希值。在需要验证密码的时候只需要计算并比对哈希值。而比特币的工作量证明也是通过不断尝试 nonce 反推哈希来实现的。</p><h1 id="Hash-通用结构">Hash 通用结构</h1><p>哈希算法总是有<a href="https://wenku.baidu.com/view/0831e189680203d8ce2f24d3.html">通用的结构</a></p><ol><li>将输入 padding 直到恰好可以分组</li><li>将输入分成 n 组 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mi>B</mi><mn>0</mn></msub><mo>…</mo><msub><mi>B</mi><mrow><mi>n</mi><mo>−</mo><mn>1</mn></mrow></msub></mrow><annotation encoding="application/x-tex">B_{0} \ldots B_{n-1}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.891661em;vertical-align:-0.208331em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.05017em;">B</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.30110799999999993em;"><span style="top:-2.5500000000000003em;margin-left:-0.05017em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">0</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.16666666666666666em;"></span><span class="minner">…</span><span class="mspace" style="margin-right:0.16666666666666666em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.05017em;">B</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.301108em;"><span style="top:-2.5500000000000003em;margin-left:-0.05017em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight">n</span><span class="mbin mtight">−</span><span class="mord mtight">1</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.208331em;"><span></span></span></span></span></span></span></span></span></span></li><li>对初始 iv 置数</li><li>迭代压缩函数 <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>I</mi><msub><mi>V</mi><mi>n</mi></msub><mo>=</mo><mi>C</mi><mi>F</mi><mrow><mo fence="true">(</mo><mi>I</mi><msub><mi>V</mi><mrow><mi>n</mi><mo>−</mo><mn>1</mn></mrow></msub><mo separator="true">,</mo><msub><mi>B</mi><mrow><mi>n</mi><mo>−</mo><mn>1</mn></mrow></msub><mo fence="true">)</mo></mrow></mrow><annotation encoding="application/x-tex">IV_{n}=CF \left( IV_{n-1},B_{n-1} \right)</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.83333em;vertical-align:-0.15em;"></span><span class="mord mathnormal" style="margin-right:0.07847em;">I</span><span class="mord"><span class="mord mathnormal" style="margin-right:0.22222em;">V</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.151392em;"><span style="top:-2.5500000000000003em;margin-left:-0.22222em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight">n</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span></span><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord mathnormal" style="margin-right:0.07153em;">C</span><span class="mord mathnormal" style="margin-right:0.13889em;">F</span><span class="mspace" style="margin-right:0.16666666666666666em;"></span><span class="minner"><span class="mopen delimcenter" style="top:0em;">(</span><span class="mord mathnormal" style="margin-right:0.07847em;">I</span><span class="mord"><span class="mord mathnormal" style="margin-right:0.22222em;">V</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.301108em;"><span style="top:-2.5500000000000003em;margin-left:-0.22222em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight">n</span><span class="mbin mtight">−</span><span class="mord mtight">1</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.208331em;"><span></span></span></span></span></span></span><span class="mpunct">,</span><span class="mspace" style="margin-right:0.16666666666666666em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.05017em;">B</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.301108em;"><span style="top:-2.5500000000000003em;margin-left:-0.05017em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight">n</span><span class="mbin mtight">−</span><span class="mord mtight">1</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.208331em;"><span></span></span></span></span></span></span><span class="mclose delimcenter" style="top:0em;">)</span></span></span></span></span></li><li>最后一个 IV 就是 Hash 值</li></ol><h1 id="SM3-实现">SM3 实现</h1><p>按照文档和网上查到的资料，我也实现了一个 SM3。<a href="https://github.com/fengkx/sm3">代码放在了 GitHub</a>。头文件如下。里面的几个函数分别对应其中的几个步骤。</p><pre class="shiki github-light" style="background-color:#fff;color:#24292e" tabindex="0"><code class="language-c"><span class="line"><span style="color:#D73A49">#ifndef</span><span style="color:#6F42C1"> _sm3_H_</span></span><span class="line"><span style="color:#D73A49">#define</span><span style="color:#6F42C1"> _sm3_H_</span></span><span class="line"></span><span class="line"><span style="color:#D73A49">#include</span><span style="color:#032F62"> &#x3C;stddef.h></span></span><span class="line"></span><span class="line"><span style="color:#D73A49">typedef</span><span style="color:#D73A49"> unsigned</span><span style="color:#D73A49"> char</span><span style="color:#24292E"> byte;</span></span><span class="line"><span style="color:#D73A49">typedef</span><span style="color:#D73A49"> unsigned</span><span style="color:#D73A49"> int</span><span style="color:#24292E"> word;</span></span><span class="line"></span><span class="line"><span style="color:#6A737D">// padding</span></span><span class="line"><span style="color:#24292E">byte </span><span style="color:#D73A49">*</span><span style="color:#6F42C1">padding</span><span style="color:#24292E">(</span><span style="color:#D73A49">const</span><span style="color:#24292E"> byte </span><span style="color:#D73A49">*</span><span style="color:#E36209">src</span><span style="color:#24292E">, </span><span style="color:#D73A49">size_t</span><span style="color:#E36209"> src_len</span><span style="color:#24292E">, </span><span style="color:#D73A49">size_t</span><span style="color:#D73A49"> *</span><span style="color:#E36209">out_len</span><span style="color:#24292E">);</span></span><span class="line"></span><span class="line"><span style="color:#6A737D">// 生成拓展 Block CF 需要用到</span></span><span class="line"><span style="color:#24292E">word </span><span style="color:#D73A49">*</span><span style="color:#6F42C1">expand_blk</span><span style="color:#24292E">(</span><span style="color:#D73A49">const</span><span style="color:#D73A49"> char</span><span style="color:#D73A49"> *</span><span style="color:#E36209">block</span><span style="color:#24292E">, word </span><span style="color:#D73A49">**</span><span style="color:#E36209">W_p</span><span style="color:#24292E">);</span></span><span class="line"></span><span class="line"><span style="color:#D73A49">void</span><span style="color:#6F42C1"> reverse_by_byte</span><span style="color:#24292E">(byte </span><span style="color:#D73A49">*</span><span style="color:#D73A49"> const</span><span style="color:#E36209"> le</span><span style="color:#24292E">, </span><span style="color:#D73A49">size_t</span><span style="color:#E36209"> len</span><span style="color:#24292E">);</span></span><span class="line"></span><span class="line"><span style="color:#6A737D">// 初始化 IV</span></span><span class="line"><span style="color:#D73A49">void</span><span style="color:#6F42C1"> iv_init</span><span style="color:#24292E">(word </span><span style="color:#D73A49">*</span><span style="color:#D73A49"> const</span><span style="color:#E36209"> ivs</span><span style="color:#24292E">);</span></span><span class="line"></span><span class="line"><span style="color:#6A737D">// CF</span></span><span class="line"><span style="color:#D73A49">void</span><span style="color:#6F42C1"> CF</span><span style="color:#24292E">(word </span><span style="color:#D73A49">*</span><span style="color:#D73A49"> const</span><span style="color:#E36209"> iv</span><span style="color:#24292E">, byte</span><span style="color:#D73A49">*</span><span style="color:#E36209"> blk</span><span style="color:#24292E">, </span><span style="color:#D73A49">const</span><span style="color:#24292E"> word </span><span style="color:#D73A49">*</span><span style="color:#D73A49"> const</span><span style="color:#E36209"> W</span><span style="color:#24292E">, </span><span style="color:#D73A49">const</span><span style="color:#24292E"> word </span><span style="color:#D73A49">*</span><span style="color:#D73A49"> const</span><span style="color:#E36209"> W_p</span><span style="color:#24292E">);</span></span><span class="line"></span><span class="line"><span style="color:#24292E">byte </span><span style="color:#D73A49">*</span><span style="color:#6F42C1">sm3</span><span style="color:#24292E">(</span><span style="color:#D73A49">const</span><span style="color:#D73A49"> char</span><span style="color:#D73A49"> *</span><span style="color:#D73A49"> const</span><span style="color:#E36209"> s</span><span style="color:#24292E">);</span></span><span class="line"></span><span class="line"><span style="color:#D73A49">#endif</span></span></code></pre><p>下面从依次分析这几个步骤<br>操作的层次或者说对象各有不同，主要分为 byte 和 word（根据文档中的定义是 4byte）</p><h1 id="具体步骤">具体步骤</h1><h2 id="padding">padding</h2><p>padding 的操作对象是 byte，在这一层次上可以忽略大小端。因为对象是一个 byte</p><p>Padding 的规则在文档中写的很清楚。</p><blockquote><p>假设消息 m 的长度为 l 比特。首先将比特“1”添加到消息的末尾，再添加 k 个“0”，k 是满 足 l + 1 + k ≡ 448mod512 的最小的非负整数。然后再添加一个 64 位比特串，该比特串是长度 l 的二进 制表示。填充后的消息 m′ 的比特长度为 512 的倍数。</p></blockquote><p>实现上我们首先需要算出最终输出的长度来分配内存。关键需要计算出填充的 0 的个数 k。k 可以通过<code>(448 - ((l % 512) + 1)  + 512) % 512</code>算出。加上 512 再模 512 可以避免得到负数的情况。然后只需要分配合适大小的内存置零并将 m 复制过去。这样我们填充 0 的部分和明文部分就算做好了。</p><p>然后还有两个步骤，明文后面的一位需要置 1，然后最后的 64 位需要置为明文长度的二进制表示。</p><p>明文后一 byte 可以通过 m[l]得到，然后只需要对这一位跟 0x80 做位或运算就好了。<br>而最后的 64 位长度表示则有大小端的差别在小端机器上需要先转换为大端表示。</p><h2 id="拓展消息">拓展消息</h2><p>拓展消息这里就是按照文档套公式。在实现上由于操作对象变成了 word 需要注意的是大小端问题。因为牵涉到字的位移操作所以大小端尤为重要。在我这个实现中 padding 产生的 bytes 是按照大端排列的。所以需要先转成小端再计算。</p><h2 id="CF-压缩函数">CF 压缩函数</h2><p>CF 也是照着文档套公式实现。因为 + 是模 2^32 加法，所以我们应该选择 unsigned int 来作为 word 的具体数字类型。之后就没有什么特别需要注意了。</p><h1 id="注意的点">注意的点</h1><h2 id="大小端判断和转换">大小端判断和转换</h2><p>大小端的判断参考了<a href="https://blog.csdn.net/a344288106/article/details/80094878">大佬的博文</a>。通过在内存中分配一个 int 变量，通过强转它的指针为 char 判断低地址 byte 的值判断机器是大端还是小端。</p><p>例如</p><pre class="shiki github-light" style="background-color:#fff;color:#24292e" tabindex="0"><code class="language-c"><span class="line"><span style="color:#D73A49">static</span><span style="color:#D73A49"> const</span><span style="color:#D73A49"> int</span><span style="color:#24292E"> endianTest </span><span style="color:#D73A49">=</span><span style="color:#D73A49"> 0x</span><span style="color:#005CC5">12345678</span><span style="color:#24292E">;</span></span></code></pre><p>大端中表示为 0x12 0x34 0x56 0x78<br>小端中表示为 0x78 0x56 0x34 0x12</p><p>转换也是通过强转指针为 char，参考以下代码</p><pre class="shiki github-light" style="background-color:#fff;color:#24292e" tabindex="0"><code class="language-c"><span class="line"><span style="color:#D73A49">void</span><span style="color:#6F42C1"> reverse_by_byte</span><span style="color:#24292E">(byte </span><span style="color:#D73A49">*</span><span style="color:#D73A49"> const</span><span style="color:#E36209"> le</span><span style="color:#24292E">, </span><span style="color:#D73A49">size_t</span><span style="color:#E36209"> len</span><span style="color:#24292E">) &#123;</span></span><span class="line"><span style="color:#D73A49">for</span><span style="color:#24292E"> (</span><span style="color:#D73A49">size_t</span><span style="color:#24292E"> i</span><span style="color:#D73A49">=</span><span style="color:#005CC5">0</span><span style="color:#24292E">;i</span><span style="color:#D73A49">&#x3C;</span><span style="color:#24292E">len</span><span style="color:#D73A49">>></span><span style="color:#005CC5">1</span><span style="color:#24292E">;i</span><span style="color:#D73A49">++</span><span style="color:#24292E">) &#123;</span></span><span class="line"><span style="color:#24292E">byte tmp </span><span style="color:#D73A49">=</span><span style="color:#E36209"> le</span><span style="color:#24292E">[i];</span></span><span class="line"><span style="color:#E36209">le</span><span style="color:#24292E">[i] </span><span style="color:#D73A49">=</span><span style="color:#E36209"> le</span><span style="color:#24292E">[len</span><span style="color:#D73A49">-</span><span style="color:#005CC5">1</span><span style="color:#D73A49">-</span><span style="color:#24292E">i];</span></span><span class="line"><span style="color:#E36209">le</span><span style="color:#24292E">[len</span><span style="color:#D73A49">-</span><span style="color:#005CC5">1</span><span style="color:#D73A49">-</span><span style="color:#24292E">i]</span><span style="color:#D73A49">=</span><span style="color:#24292E">tmp;</span></span><span class="line"><span style="color:#24292E">&#125;</span></span><span class="line"><span style="color:#24292E">&#125;</span></span></code></pre><h2 id="循环移位">循环移位</h2><pre class="shiki github-light" style="background-color:#fff;color:#24292e" tabindex="0"><code class="language-c"><span class="line"><span style="color:#D73A49">#define</span><span style="color:#6F42C1"> ls_w</span><span style="color:#24292E">(</span><span style="color:#E36209">a</span><span style="color:#24292E">, </span><span style="color:#E36209">n</span><span style="color:#24292E">) (a </span><span style="color:#D73A49">>></span><span style="color:#24292E"> (</span><span style="color:#005CC5">32</span><span style="color:#D73A49"> -</span><span style="color:#24292E"> n) </span><span style="color:#D73A49">|</span><span style="color:#24292E"> a </span><span style="color:#D73A49">&#x3C;&#x3C;</span><span style="color:#24292E"> n)</span></span></code></pre><p>先保留剩下的位再和要移过去的位或</p><h2 id="用移位代替除法">用移位代替除法</h2><p>为了效率我们可以使用右移代替除法。乘法<a href="https://github.com/0xffff-one/0xffff-lib/pull/2#discussion_r311075793">一般都能被编译器优化掉</a>。但是除法因为<code>DIV</code>指令会将余数放到寄存器里面所以不能优化掉，可以用右移代替。</p><h1 id="体会">体会</h1><p>算法的实现方面其实不多考虑效率单纯实现的话不难。比较需要注意的就是大小端的问题。C 语言可以直接操作内存所以还是比较舒服的。<br>至于算法的原理，为什么要那样设计就不知道了。这也应该是正式上课的时候应该注意的问题。</p><h1 id="参考文章">参考文章</h1><ul><li><a href="https://github.com/fengkx/sm3/blob/master/SM3%E5%AF%86%E7%A0%81%E6%9D%82%E5%87%91%E7%AE%97%E6%B3%95.pdf">SM3 文档</a></li><li><a href="https://blog.csdn.net/a344288106/article/details/80094878">SM3 密码杂凑算法实现及说明 by  梁默鱼</a></li><li><a href="https://stackoverflow.com/questions/19068705/undefined-reference-when-calling-inline-function">undefined reference when calling inline function</a></li><li><a href="https://stackoverflow.com/questions/30552039/calculating-k-for-padding-of-sha-256-message">Calculating k for padding of SHA-256 message</a></li></ul>]]></content><summary type="html">&lt;p&gt;这学期欧气爆棚，有幸在第一轮选到了密码学研学项目的非正式课程。第一个要学习的算法是&lt;a href=&quot;http://www.sca.gov.cn/sca/xwdt/2010-12/17/content_1002389.shtml&quot;&gt;SM3&lt;/a&gt;&lt;/p&gt;
&lt;h1 id=&quot;SM</summary><category term="Crypto" scheme="https://www.fengkx.top/tags/Crypto/"/><category term="SM3" scheme="https://www.fengkx.top/tags/SM3/"/><category term="哈希" scheme="https://www.fengkx.top/tags/%E5%93%88%E5%B8%8C/"/></entry><entry><title>系统设置汇总</title><link href="https://www.fengkx.top/post/setup-system/"/><id>https://www.fengkx.top/post/setup-system/</id><published>2019-09-12T22:43:03.000Z</published><updated>2026-03-01T16:09:07.668Z</updated><content type="html"><![CDATA[<p>装系统这事干的多了，工多艺熟装个系统就跟玩似得。但是系统装好了不能直接用呀，至少直接用起来不够爽。熟悉的环境和配置搞起来比装系统本身更麻烦更耗时间。当然自从从双系统换回虚拟机之后，得益于虚拟机的快照功能整个环境都很少会去配置了。但是为了减少重复劳动在这里记录一下。</p><p>PS: 系统使用的是<a href="https://osdn.net/projects/manjaro-community/storage/deepin/">Manjaro-deepin</a><br>持续更新：LastUpdated At 2019/9/12</p><h2 id="pacman-yay">pacman yay</h2><p>Manjaro 属于 Arch 系，AUR 的包是真的多而且更新又快，这真的是最大的亮点。新装的系统第一件事当然是更新了。为了能在天朝好好过日子，换源是必须的。</p><pre class="shiki github-light" style="background-color:#fff;color:#24292e" tabindex="0"><code class="language-shell"><span class="line"><span style="color:#6F42C1">sudo</span><span style="color:#032F62"> pacman-mirrors</span><span style="color:#005CC5"> -i</span><span style="color:#005CC5"> -c</span><span style="color:#032F62"> China</span><span style="color:#005CC5"> -m</span><span style="color:#032F62"> rank</span><span style="color:#6A737D"> #换源</span></span></code></pre><p>修改<code>/etc/pamac.conf</code>,在末尾添加</p><pre class="shiki github-light" style="background-color:#fff;color:#24292e" tabindex="0"><code class="language-ini"><span class="line"><span style="color:#6F42C1">[archlinuxcn]</span></span><span class="line"><span style="color:#D73A49">SigLevel</span><span style="color:#24292E"> = Optional TrustedOnly</span></span><span class="line"><span style="color:#D73A49">Server</span><span style="color:#24292E"> = https://mirrors.tuna.tsinghua.edu.cn/archlinuxcn/$arch</span></span></code></pre><p>用于添加<code>archlinuxcn</code>源。</p><pre class="shiki github-light" style="background-color:#fff;color:#24292e" tabindex="0"><code class="language-shell"><span class="line"><span style="color:#6F42C1">sudo</span><span style="color:#032F62"> pacman</span><span style="color:#005CC5"> -Sy</span><span style="color:#6A737D"> #更新数据库</span></span><span class="line"><span style="color:#6F42C1">sudo</span><span style="color:#032F62"> pacman</span><span style="color:#005CC5"> -S</span><span style="color:#032F62"> archlinuxcn-keyring</span><span style="color:#6A737D"> #更新keyring</span></span><span class="line"><span style="color:#6F42C1">sudo</span><span style="color:#032F62"> pacman</span><span style="color:#005CC5"> -S</span><span style="color:#032F62"> yay</span><span style="color:#6A737D"> #安装yay</span></span></code></pre><h2 id="VritualBox-相关配置">VritualBox 相关配置</h2><p>在安装前应配置显存到最大的 128MB，显卡控制器配置为<code>VBoxSVGA</code>，并且启用 3D 加速。<br>安装之后不要使用 VirtualBox 安装增强功能的 ISO 安装 guest os module。应该使用包管理器，从源里安装内核对应的版本。</p><pre class="shiki github-light" style="background-color:#fff;color:#24292e" tabindex="0"><code class="language-shell"><span class="line"><span style="color:#6F42C1">uname</span><span style="color:#005CC5"> -r</span><span style="color:#6A737D"> #查看内核版本</span></span><span class="line"><span style="color:#6F42C1">yay</span><span style="color:#032F62"> virtualbox-guest-modules</span><span style="color:#6A737D"> #从中选择对应版本安装</span></span><span class="line"><span style="color:#6F42C1">yay</span><span style="color:#032F62"> virtualbox-guest-utils</span></span></code></pre><h2 id="Home-目录下文件转成英文">Home 目录下文件转成英文</h2><pre class="shiki github-light" style="background-color:#fff;color:#24292e" tabindex="0"><code class="language-shell"><span class="line"><span style="color:#D73A49">export</span><span style="color:#24292E"> LANG</span><span style="color:#D73A49">=</span><span style="color:#24292E">en_US</span></span><span class="line"><span style="color:#6F42C1">xdg-user-dirs-gtk-update</span></span></code></pre><h2 id="输入法">输入法</h2><pre class="shiki github-light" style="background-color:#fff;color:#24292e" tabindex="0"><code class="language-shell"><span class="line"><span style="color:#6F42C1">sudo</span><span style="color:#032F62"> pacman</span><span style="color:#005CC5"> -S</span><span style="color:#032F62"> fcitx-sogoupinyin</span></span><span class="line"><span style="color:#6F42C1">sudo</span><span style="color:#032F62"> pacman</span><span style="color:#005CC5"> -S</span><span style="color:#032F62"> fcitx-im</span><span style="color:#6A737D">     # 全部安装</span></span><span class="line"><span style="color:#6F42C1">sudo</span><span style="color:#032F62"> pacman</span><span style="color:#005CC5"> -S</span><span style="color:#032F62"> fcitx-configtool</span><span style="color:#6A737D">     # 图形化配置工具</span></span></code></pre><h2 id="xprofile">.xprofile</h2><p>在<code>~/.xprofile</code>添加</p><pre class="shiki github-light" style="background-color:#fff;color:#24292e" tabindex="0"><code class="language-text"><span class="line"><span>export LC_CTYPE=zh_CN.UTF-8</span></span><span class="line"><span>export XMODIFIERS=@im=fcitx</span></span><span class="line"><span>export GTK_IM_MODULE=fcitx</span></span><span class="line"><span>exportQT_IM_MODULE=fcitx</span></span></code></pre><h2 id="proxychains">proxychains</h2><pre class="shiki github-light" style="background-color:#fff;color:#24292e" tabindex="0"><code class="language-shell"><span class="line"><span style="color:#6F42C1">yay</span><span style="color:#005CC5"> -S</span><span style="color:#032F62"> proxychains-ng</span></span></code></pre><p>在<code>/etc/proxychains.conf</code>末尾添加</p><pre class="shiki github-light" style="background-color:#fff;color:#24292e" tabindex="0"><code class="language-ini"><span class="line"><span style="color:#24292E">socks5  10.0.2.2  1080</span></span></code></pre><h2 id="至此可以重启一下了">至此可以重启一下了</h2><h2 id="shell">shell</h2><p>Shell 用的是<code>zsh</code></p><pre class="shiki github-light" style="background-color:#fff;color:#24292e" tabindex="0"><code class="language-shell"><span class="line"><span style="color:#6F42C1">yay</span><span style="color:#005CC5"> -S</span><span style="color:#032F62"> zsh</span></span><span class="line"><span style="color:#6F42C1">chsh</span><span style="color:#005CC5"> -s</span><span style="color:#032F62"> /usr/bin/zsh</span></span></code></pre><p>配置方面没有用<code>oh-my-zsh</code>而是用的<a href="https://github.com/zdharma/zplugin">zplugin</a>。就是为了速度快。<br>配置放在了<a href="https://github.com/fengkx/zshrc">这里</a></p><pre class="shiki github-light" style="background-color:#fff;color:#24292e" tabindex="0"><code class="language-shell"><span class="line"><span style="color:#6F42C1">git</span><span style="color:#032F62"> clone</span><span style="color:#032F62"> git@github.com:fengkx/zshrc.git</span><span style="color:#032F62"> ~/.zsh</span></span><span class="line"><span style="color:#6F42C1">ln</span><span style="color:#005CC5"> -s</span><span style="color:#24292E"> $HOME</span><span style="color:#032F62">/.zsh/zshrc</span><span style="color:#032F62"> ~/.zshrc</span></span></code></pre><h2 id="Node">Node</h2><ol><li><a href="https://github.com/nvm-sh/nvm">nvm</a><br>source 了<code>~/.zshrc</code>之后就应该安装了</li></ol><pre class="shiki github-light" style="background-color:#fff;color:#24292e" tabindex="0"><code class="language-shell"><span class="line"><span style="color:#6F42C1">nvm</span><span style="color:#032F62"> install</span><span style="color:#005CC5"> --lts</span><span style="color:#6A737D"> #安装最新LTS版本</span></span></code></pre><ol start="2"><li>nrm</li></ol><pre class="shiki github-light" style="background-color:#fff;color:#24292e" tabindex="0"><code class="language-shell"><span class="line"><span style="color:#6F42C1">npm</span><span style="color:#032F62"> i</span><span style="color:#005CC5"> -g</span><span style="color:#032F62"> nrm</span><span style="color:#6A737D"> #安装nrm</span></span><span class="line"><span style="color:#6F42C1">nrm</span><span style="color:#032F62"> use</span><span style="color:#032F62"> taobao</span><span style="color:#6A737D"> #使用淘宝源</span></span></code></pre><h2 id="neovim">neovim</h2><p>配置放在了<a href="https://github.com/fengkx/vimrc">这里</a></p><pre class="shiki github-light" style="background-color:#fff;color:#24292e" tabindex="0"><code class="language-shell"><span class="line"><span style="color:#6F42C1">yay</span><span style="color:#005CC5"> -S</span><span style="color:#032F62"> neovim-nightly</span><span style="color:#6A737D"> #安装neovim nightly</span></span><span class="line"><span style="color:#6F42C1">git</span><span style="color:#032F62"> clone</span><span style="color:#032F62"> git@github.com:fengkx/vimrc.git</span><span style="color:#032F62"> ~/.config/nvim</span></span></code></pre><h3 id="vim-中的一些插件配置">vim 中的一些插件配置</h3><p>vim-go 需要 <code>:GoInstallBinaries</code><br>gopls 可能需要自己本地<code>go build</code>然后放在 bin 目录才能用</p><pre class="shiki github-light" style="background-color:#fff;color:#24292e" tabindex="0"><code class="language-shell"><span class="line"><span style="color:#6F42C1">git</span><span style="color:#032F62"> clone</span><span style="color:#032F62"> https://github.com/golang/xerrors</span><span style="color:#6A737D"> #编译需要</span></span><span class="line"><span style="color:#6F42C1">go</span><span style="color:#032F62"> get</span><span style="color:#005CC5"> -u</span><span style="color:#032F62"> golang.org/x/tools/cmd/gopls</span></span><span class="line"><span style="color:#24292E">GOMODULE</span><span style="color:#D73A49">=</span><span style="color:#032F62">on</span><span style="color:#6F42C1"> go</span><span style="color:#032F62"> build</span></span></code></pre><p>coc extension list</p><pre class="shiki github-light" style="background-color:#fff;color:#24292e" tabindex="0"><code class="language-text"><span class="line"><span>coc-emmet</span></span><span class="line"><span>coc-translator</span></span><span class="line"><span>coc-word</span></span><span class="line"><span>coc-git</span></span><span class="line"><span>coc-python</span></span><span class="line"><span>coc-yaml</span></span></code></pre><h2 id="Python">Python</h2><p><a href="https://www.fengkx.top/post/pyenv-and-virtualenv/">https://www.fengkx.top/post/pyenv-and-virtualenv/</a></p><p>Pyhon 安装包镜像。版本号可以替换</p><pre class="shiki github-light" style="background-color:#fff;color:#24292e" tabindex="0"><code class="language-text"><span class="line"><span>https://mirrors.sohu.com/python/3.7.4/</span></span></code></pre><h2 id="Go">Go</h2><pre class="shiki github-light" style="background-color:#fff;color:#24292e" tabindex="0"><code class="language-shell"><span class="line"><span style="color:#D73A49">export</span><span style="color:#24292E"> GOPROXY</span><span style="color:#D73A49">=</span><span style="color:#24292E">https://goproxy.io </span><span style="color:#6A737D">#设置GoProxy</span></span><span class="line"><span style="color:#D73A49">export</span><span style="color:#24292E"> GOMODULE</span><span style="color:#D73A49">=</span><span style="color:#24292E">on </span><span style="color:#6A737D">#启用 Go mod</span></span></code></pre><h2 id="Docker">Docker</h2><p><a href="https://www.daocloud.io/mirror#accelerator-doc">DaoCloud 文档在这</a><br>在<code>/etc/docker/daemon.json</code> 添加</p><pre class="shiki github-light" style="background-color:#fff;color:#24292e" tabindex="0"><code class="language-json"><span class="line"><span style="color:#24292E">&#123;</span></span><span class="line"><span style="color:#005CC5">  "registry-mirror"</span><span style="color:#24292E">: </span><span style="color:#032F62">"http://f1361db2.m.daocloud.io"</span></span><span class="line"><span style="color:#24292E">&#125;</span></span></code></pre>]]></content><summary type="html">&lt;p&gt;装系统这事干的多了，工多艺熟装个系统就跟玩似得。但是系统装好了不能直接用呀，至少直接用起来不够爽。熟悉的环境和配置搞起来比装系统本身更麻烦更耗时间。当然自从从双系统换回虚拟机之后，得益于虚拟机的快照功能整个环境都很少会去配置了。但是为了减少重复劳动在这里记录一下。&lt;/p&gt;
</summary><category term="Linux" scheme="https://www.fengkx.top/tags/Linux/"/></entry><entry><title>评论系统折腾录 2</title><link href="https://www.fengkx.top/post/comment-system2/"/><id>https://www.fengkx.top/post/comment-system2/</id><published>2019-06-28T20:04:50.000Z</published><updated>2026-03-01T16:09:07.664Z</updated><content type="html"><![CDATA[<h1 id="起因">起因</h1><p>最近 LeanCloud<a href="https://blog.avoscloud.com/6812/">域名被有关部门停止解析了</a>。一纸行政命令下来说停就停真的惨。在国内想要建个网站，做个服务不容易啊。为了合法 LeanCloud 现在<a href="https://blog.avoscloud.com/6939/">要求用户绑定自己的域名</a>当然了，肯定是要实名备案过的。</p><h1 id="影响">影响</h1><p>我的博客之前<a href="https://www.fengkx.top/post/comment-system/">用了一段时间的 Valine 作为评论系统</a>它使用 LeanCloud 作为后端存储，本来以为是稳稳的了，但是现在看来不备案就要凉了。</p><p>但是备案太麻烦了，我也不想实名备案。总感觉备案之后这里就要接受审查。有种<code>Big brother is watching you</code>的感觉。所以评论系统又要换了。想了想用 GitHub Issue 的方案应该是最稳的。于是乎选择了<a href="https://gitalk.github.io">GitTalk</a>。坏处就是要有 GitHub 帐号才能评论了。Valine 的头像使用 gavatar，整体实现的效果其实一般。有帐号要求其实效果可以更好。GitHub Issue 我相信还是很稳的。(<s>奶一口</s>)</p><p>从 LeanCloud 管理界面可以看到 23 条评论，可惜了。</p><h2 id="更换至-pure-主题">更换至 pure 主题</h2><p>一不做二不休，打算把主题也更新一下。之前的主题一直是 Next5.0。尝试了一下升级 Next 主题大版本的过程实在是太痛苦了。mathjax, wordcount 等插件瞬间爆炸。因为主题装了很多东西，自己改动的也不少。网页的加载速度其实不太妙，全靠 Service Worker 挑大梁。那么既然这样干脆换一个主题吧。于是找到了<a href="https://github.com/cofess/hexo-theme-pure/">Pure</a>。该有的东西 wordcount,mathjax 一个不缺，还有 repository 这样的贴心功能。而且好看，简洁。加载速度瞬间上了一个档次。可以说是非常满意了。</p><p>所以说不评论一下试试？</p>]]></content><summary type="html">&lt;h1 id=&quot;起因&quot;&gt;起因&lt;/h1&gt;
&lt;p&gt;最近 LeanCloud&lt;a href=&quot;https://blog.avoscloud.com/6812/&quot;&gt;域名被有关部门停止解析了&lt;/a&gt;。一纸行政命令下来说停就停真的惨。在国内想要建个网站，做个服务不容易啊。为了合法 LeanC</summary><category term="Hexo" scheme="https://www.fengkx.top/tags/Hexo/"/><category term="评论系统" scheme="https://www.fengkx.top/tags/%E8%AF%84%E8%AE%BA%E7%B3%BB%E7%BB%9F/"/></entry><entry><title>CSAPP Attack Lab</title><link href="https://www.fengkx.top/post/csapp-attack-lab/"/><id>https://www.fengkx.top/post/csapp-attack-lab/</id><published>2019-05-28T19:01:55.000Z</published><updated>2026-03-01T16:09:07.664Z</updated><content type="html"><![CDATA[<p><a href="https://www.fengkx.top/2019/05/08/csapp-bomb-lab/">Bomb Lab</a> 之后做了 Attack Lab。下面又来复盘回顾一下。</p><h1 id="准备">准备</h1><p><a href="http://csapp.cs.cmu.edu/3e/labs.html">这里</a> 下载 handout 和 writeup。</p><p>writeup 很重要，一定得看的。解压之后我们可以看到<code>ctarget</code>和 <code>rtarget</code>。分别对应前 3 个 phrase 和后两个 phrase 还有一个用于将 16 进制表示的 byte 转化对应字符串的工具<code>hex2raw</code>。</p><p>用法在 writeup 里面有写。简单地说就是标准输入中输入两个一组的十六进制数字表示的 byte。每个 byte 由一个或多个空格分割还可以用<code>/**/</code>写注释。输出为对应的字符串。因为有一些不可见字符键盘是打不出来的所以很有用。</p><p>我们可以通过管道或者重定向的操作灵活的使用它</p><p>writeup 还讲到了利用<code>gcc</code>和<code>objdump</code>生成机器码的方法</p><h1 id="通关过程">通关过程</h1><p>首先要先分析程序。我们的输入是过<code>getbuf</code>这个函数读进去的。汇编代码长这样</p><pre class="shiki github-light" style="background-color:#fff;color:#24292e" tabindex="0"><code class="language-asm"><span class="line"><span style="color:#24292E">Dump of assembler code for function </span><span style="color:#6F42C1">getbuf:</span></span><span class="line"><span style="color:#24292E">=> </span><span style="color:#005CC5">0x00000000004017a8</span><span style="color:#24292E"> &#x3C;+</span><span style="color:#005CC5">0</span><span style="color:#24292E">>:</span><span style="color:#D73A49">sub</span><span style="color:#24292E">    $0x28,%</span><span style="color:#005CC5">rsp</span></span><span class="line"><span style="color:#005CC5">   0x00000000004017ac</span><span style="color:#24292E"> &#x3C;+</span><span style="color:#005CC5">4</span><span style="color:#24292E">>:</span><span style="color:#D73A49">mov</span><span style="color:#24292E">    %</span><span style="color:#005CC5">rsp</span><span style="color:#24292E">,%</span><span style="color:#005CC5">rdi</span></span><span class="line"><span style="color:#005CC5">   0x00000000004017af</span><span style="color:#24292E"> &#x3C;+</span><span style="color:#005CC5">7</span><span style="color:#24292E">>:callq  </span><span style="color:#005CC5">0x401a40</span><span style="color:#24292E"> &#x3C;Gets></span></span><span class="line"><span style="color:#005CC5">   0x00000000004017b4</span><span style="color:#24292E"> &#x3C;+</span><span style="color:#005CC5">12</span><span style="color:#24292E">>:</span><span style="color:#D73A49">mov</span><span style="color:#24292E">    $0x1,%</span><span style="color:#005CC5">eax</span></span><span class="line"><span style="color:#005CC5">   0x00000000004017b9</span><span style="color:#24292E"> &#x3C;+</span><span style="color:#005CC5">17</span><span style="color:#24292E">>:</span><span style="color:#D73A49">add</span><span style="color:#24292E">    $0x28,%</span><span style="color:#005CC5">rsp</span></span><span class="line"><span style="color:#005CC5">   0x00000000004017bd</span><span style="color:#24292E"> &#x3C;+</span><span style="color:#005CC5">21</span><span style="color:#24292E">>:retq</span></span><span class="line"><span style="color:#24292E">End of assembler dump.</span></span><span class="line"></span></code></pre><p>函数分配了<code>0x28</code>也就是 40bytes 的缓冲区，将输入读入到栈中。然后调用<code>ret</code></p><p>栈空间大概长这样</p><p><img loading="lazy" src="https://vip2.loli.io/2023/03/08/zjJDcImA2MVKW4P.png" alt="stack"></p><p>栈是向低地址增长的。代码的运行方向是是从小到大。栈是从栈顶<code>%rsp</code>开始向高地址写的。</p><p>超过 40 个 byte 的部分就会覆盖掉返回地址<br><code>ctarget</code> 和<code>rtarget``getbuf</code> 部分都是一样的，下面的 phrase 的区别就是要往栈里面放些什么东西让程序执行到我们想要执行到的地方。</p><h2 id="phrase1">phrase1</h2><p>只要调用 touch1 直接在 40bytes 的任意字符后面放 touch1 的地址就行了。将 touch1 地址写成小端形式输到<code>hex2raw</code>就能生成。</p><h2 id="phrase2">phrase2</h2><p>这次函数多了一个参数，还判断它和 cookie 一致。那么我们要让 <code>%rdi</code> 等于<code>cookie</code>。我们需要插入代码进去执行。writeup 里说我们在所有控制转移的时候都要通过<code>ret</code></p><pre class="shiki github-light" style="background-color:#fff;color:#24292e" tabindex="0"><code class="language-asm"><span class="line"><span style="color:#D73A49">mov</span><span style="color:#24292E"> $0x59b997fa, %</span><span style="color:#005CC5">rdi</span></span><span class="line"><span style="color:#D73A49">ret</span></span></code></pre><p>写出汇编代码<code>gcc -c</code>配合<code>objdump</code>拿到 hex 表示。40 个填充字符后的第一个四字写代码所在的地址。后一个四字就是插入的代码中<code>ret</code>跳回的地址。所以后一个四字写 touch2 的地址</p><p><img loading="lazy" src="https://vip2.loli.io/2023/03/08/H1ZxjkrIX3yVGzS.png" alt="touch2"></p><h2 id="phrase3">phrase3</h2><p>同样是传参数判断是否与 cookie 相等。不过这此时参数是个字符串，也就是首元素的地址。</p><p><img loading="lazy" src="https://vip2.loli.io/2023/03/08/6CVYl9DEBthwm2n.png" alt="touch3"></p><p>这里插入的代码是</p><pre class="shiki github-light" style="background-color:#fff;color:#24292e" tabindex="0"><code class="language-asm"><span class="line"><span style="color:#D73A49">mov</span><span style="color:#24292E"> $0x5561dc78, %</span><span style="color:#005CC5">rdi</span><span style="color:#24292E"> #</span><span style="color:#005CC5">0x5561dc78</span><span style="color:#24292E"> = </span><span style="color:#005CC5">rsp</span></span><span class="line"><span style="color:#D73A49">ret</span></span></code></pre><h2 id="phrase4">phrase4</h2><p>phrase4 用的是<code>rtarget</code>跟<code>ctarget</code>区别就是栈地址有随机化和栈不可执行。然后就要用到 <code>rop</code>。</p><p>因为栈的代码不可执行所以利用的是已有的代码。已有的代码指的是从 byte 的角度去看待的代码。然后通过一系列<code>gadgets</code>执行到对应的函数。具体的解释 writeup 有了。<br><code>gadgets</code> 的特征就是 c3 结尾。</p><p>具体到这一关 writeup 里这么说</p><blockquote><p>All the gadgets you need can be found in the region of the code for rtarget demarcated by the functions start_farm and mid_farm.</p></blockquote><p>要点就是找到 <code>gadgets</code>。</p><p>要执行 touch2 并且把 %rdi 置成 cookie。可以直接把 cookie 写在栈里面 pop 到 <code>%rdi</code> 里面。配合 writeup 里的表找<code>gadgets</code></p><p><code>pop</code> 到 <code>%rdi</code> 要用到的<code>5f</code>没有找到。只能先<code>pop</code>到别的地方再 <code>mov</code>。</p><p>于是找到了这样的 <code>gadgets</code></p><pre class="shiki github-light" style="background-color:#fff;color:#24292e" tabindex="0"><code class="language-asm"><span class="line"><span style="color:#D73A49">pop</span><span style="color:#24292E"> %</span><span style="color:#005CC5">rax</span></span><span class="line"><span style="color:#D73A49">mov</span><span style="color:#24292E"> %</span><span style="color:#005CC5">rax</span><span style="color:#24292E">, %</span><span style="color:#005CC5">rdi</span></span></code></pre><p>对应函数和地址在这<code>90</code>对应的是<code>nop</code>所以可以忽略</p><pre class="shiki github-light" style="background-color:#fff;color:#24292e" tabindex="0"><code class="language-asm"><span class="line"><span style="color:#24292E">00000000004019a7 &#x3C;addval_219>:</span></span><span class="line"><span style="color:#B31D28;font-style:italic">  4019a7:</span><span style="color:#005CC5">8d</span><span style="color:#005CC5"> 87</span><span style="color:#005CC5"> 51</span><span style="color:#005CC5"> 73</span><span style="color:#005CC5"> 58</span><span style="color:#005CC5"> 90</span><span style="color:#D73A49">    lea</span><span style="color:#24292E">    -</span><span style="color:#005CC5">0x6fa78caf</span><span style="color:#24292E">(%</span><span style="color:#005CC5">rdi</span><span style="color:#24292E">),%</span><span style="color:#005CC5">eax</span></span><span class="line"><span style="color:#B31D28;font-style:italic">  4019ad:</span><span style="color:#24292E">c3</span></span></code></pre><pre class="shiki github-light" style="background-color:#fff;color:#24292e" tabindex="0"><code class="language-asm"><span class="line"><span style="color:#24292E">00000000004019c3 &#x3C;setval_426>:</span></span><span class="line"><span style="color:#B31D28;font-style:italic">  4019c3:</span><span style="color:#24292E">c7 </span><span style="color:#005CC5">07</span><span style="color:#005CC5"> 48</span><span style="color:#005CC5"> 89</span><span style="color:#24292E"> c7 </span><span style="color:#005CC5">90</span><span style="color:#24292E">    movl   $0x90c78948,(%</span><span style="color:#005CC5">rdi</span><span style="color:#24292E">)</span></span><span class="line"><span style="color:#B31D28;font-style:italic">  4019c9:</span><span style="color:#24292E">c3</span></span></code></pre><p>字符串的组成是</p><blockquote><p>40 offset + pop gadgets + cookie + mov gadgets + touch2 addr</p></blockquote><pre class="shiki github-light" style="background-color:#fff;color:#24292e" tabindex="0"><code class="language-text"><span class="line"><span>....offset(40 bytes)</span></span><span class="line"><span>ab 19 40 00 00 00 00 00 /* pop cookie to rax */</span></span><span class="line"><span>fa 97 b9 59 00 00 00 00 /* cookie */</span></span><span class="line"><span>c5 19 40 00 00 00 00 00 /* mov rax to rdi */</span></span><span class="line"><span>ec 17 40 00 00 00 00 00 /* touch2 */</span></span></code></pre>]]></content><summary type="html">&lt;p&gt;&lt;a href=&quot;https://www.fengkx.top/2019/05/08/csapp-bomb-lab/&quot;&gt;Bomb Lab&lt;/a&gt; 之后做了 Attack Lab。下面又来复盘回顾一下。&lt;/p&gt;
&lt;h1 id=&quot;准备&quot;&gt;准备&lt;/h1&gt;
&lt;p&gt;&lt;a href=&quot;</summary><category term="CSAPP" scheme="https://www.fengkx.top/tags/CSAPP/"/><category term="Attack Lab" scheme="https://www.fengkx.top/tags/Attack-Lab/"/></entry><entry><title>绝版好课之西樵山之旅</title><link href="https://www.fengkx.top/post/xiqiao-mountain/"/><id>https://www.fengkx.top/post/xiqiao-mountain/</id><published>2019-05-21T23:22:47.000Z</published><updated>2026-03-01T16:09:07.668Z</updated><content type="html"><![CDATA[<p>这个学期选了一门公选叫做自然地理科考探索。课程是这样的，前四周在课室听老师讲地理知识。后面老师会组织外出考察。前四周的内容老师也是偏向后面外出考察的可选地点来讲。</p><p>我报名了去西樵山的考察。原因很简单就是近。其他的路线用时间比较长，只有西樵山是可以一天来回的。不管则么说这也是我第一次参与这样的活动在地理专业的老师领着爬山。这才让我知道爬个山也能那么有趣的那么多隐藏的看点的。</p><p>下面便是报告（掺杂着点感悟）</p><h1 id="行程记录与观察">行程记录与观察</h1><h2 id="山外露头">山外露头</h2><p>5 月 12 日，早上 7 点从学校出发去往西樵山。西樵山有着国家地质公园的头衔。能成为国家地质公园当然是有他的特殊之处。其中一个重要的原因是这里原本是一座火山。</p><p>那么我们从哪里可以推测出这是一座火山呢？老师首先带我们在山的外部看<code>露头</code>。所谓的<code>露头</code>就是山露出地面的部分</p><img loading="lazy" src="https://vip2.loli.io/2023/03/09/6xaFMOkwSrXVy7d.jpg" alt="loutou.jpg" width="883" height="662"><p>上面的图因为曝光过度很模糊，但是也可以看到山上有很多<code>节理</code>（裂缝的专业术语）。形状比较规则像一个方形。据老师说这也是火山的一个特征，而且是难得一见的景观。</p><img loading="lazy" src="https://vip2.loli.io/2023/03/08/QtLGUvRCVqWkcmy.webp" alt="fangshi.jpg" width="883" height="662"><p>近看山体。上面的图的石头有一个特征就是石头像是一根根方形的石柱嵌在一起似得。这也是火山的一个特征。（大概可以想象外层遇冷凝固的岩浆留下来吧）</p><h2 id="蟹眼泉">蟹眼泉</h2><p>然后我们就正式进到山里面了。首先我们来到了蟹眼泉</p><img loading="lazy" src="https://vip2.loli.io/2023/03/08/qDnuBNKiOPXyTh2.png" alt="xieyan" width="883" height="662"><p>据说上面的泉眼像是螃蟹的眼睛。远看的话发挥一下想象力其实是像的，但是如果不被它的名字引导一下大概是想象不出来的。</p><p>这只是西樵山众多的泉眼中的其中之一。之所以有这么多泉眼与它是火山是分不开的。老师引导我们观察泉水的周围和泉眼出口本身。泉水是从石头的孔洞缝隙里流出来的，附近被青苔覆盖而且泉水上方有一个墓。从这些信息里面可以推出什么呢？</p><blockquote><ol><li>上面的墓说明上面的土质比较松软</li><li>泉水是从比较硬的石头上冒出来，青苔则说明这块比较潮湿</li></ol></blockquote><p>这里就牵涉到了火山喷发时冒出的物质了。物质分类逃不过那个套路，就是固液气。固体有石块（硬）或者火山灰（软）。液体自然是岩浆。</p><p>泉水上面一带多是火山灰，所以多是软质的土，降水可以渗到地下。泉水附近则是固体或者岩浆。因为冷却程度不均一，很容易会产生裂隙水流到一处就形成了西樵山众多的泉水。</p><p>这是今天的第一次比较完整的<strong>推理</strong>。推理也是我觉得这次旅程很有趣的地方，可以从周围的蛛丝马迹推理出数百万年前的情况。</p><img loading="lazy" src="https://vip2.loli.io/2023/03/08/SvM1y4I6ZWHaFKU.webp" alt="longxu" width="883" height="662">不远处另一个泉水<h2 id="翆岩">翆岩</h2><p>然后我们来到了<code>翆岩</code>一个山谷。山谷越往里走越窄，演这山谷有还有溪流。老师继续引导我们通过观察做推理。山谷的形成可以想象因为有东西（例如溪流）把一部分的固体侵蚀掉了。保留下来的部分与被侵蚀掉的有何不同呢？结合外宽内窄的特性。可以推理被侵蚀的是较软的火山灰，岩石或者岩浆因为物理重量原因总是比较靠近火山内部就形成了外宽内窄的情况。</p><h2 id="火山口附近">火山口附近</h2><p>继续前进越来越靠近火山口了。来到了天湖（火山口湖）附近，看到了这样一块石头。</p><p>PS: 西樵山属于裂隙式喷发不像富士山那种火山口</p><img loading="lazy" src="https://vip2.loli.io/2023/03/09/QqCIavphV7sU56r.jpg" alt="jikuai" width="883" height="662"><p>这样一块石头，如果不是有专业的老师带着我是绝对不会停下来观察的。但是即便是这样的石头也可以<strong>推理</strong>一下。观察一下可以看出石头是有几块大石头合在一起的。我们可以看到它们的连接处还有明显地痕迹（脉络）结合这块是火山口附近。大块的石头不想火山灰和小石头会飞的比较远，大石头就在火山口附近堆积起来经过地质作用之后变成了这样岩石。因为是几块岩石集合成一块所以有个专业术语叫做<code>集块岩</code>。</p><p>从这块岩石上我们还可以看到风化作用（通俗的理解成破碎）</p><ul><li>变了颜色（黄色) 说明发生了化学反应的化学风化</li><li>上面的绿色植物属于生物风化</li><li>还有无处不在的物理风化</li></ul><h2 id="采石场">采石场</h2><p>下一站我们来到了古代采石场。老师给我们科普了一下距今 7000-6000 年前，海平面比现在要高。西樵山在当时算是一座大的岛。当时的人们已经在上面渔猎开发石器了。西樵山是一座火山，自然不缺石头。但也不是什么石头都可以拿来做石器。</p><p>进入到<code>滴水岩</code>里面可以看到有两种区别很明显的石头。</p><img loading="lazy" src="https://vip2.loli.io/2023/03/08/qgJ47MsphdC1zAE.webp" alt="nihuiyan" width="883" height="662"><img loading="lazy" src="https://vip2.loli.io/2023/03/09/qyhmPiClx9cGKRu.jpg" alt="penchuyan" width="883" height="662"><p>应该都看的出来第二种石头有明显被开采过的痕迹。第二种石头更加致密自然更适合来打石器。</p><p>这两种差别的原因是因为第二种石头属于<code>喷出岩</code>没有时间与别的物质反应就已经凝固了。</p><p>这里可以看到<code>长石</code>的身影。后面老师更我们科普了一下花岗岩（斜长石）和带点红色的钾长石的区别。从化学元素的角度来说斜长石的 Na 或 Ca 被 K 顶掉了。泛红的正长石会带有放射性，在室内装修的时候要谨慎。</p><img loading="lazy" src="https://vip2.loli.io/2023/03/08/8ZRw4N3p9K5HICy.webp" alt="山洞深处" width="883" height="662">山洞深处<h2 id="生物风化">生物风化</h2><p>后面我们还观察了山里面风化现象特别是生物导致的风化现象比较明显的一处。</p><p>&lt;img src=<a href="https://vip2.loli.io/2023/03/09/4ca7qFRtHkpf1li.webp">https://vip2.loli.io/2023/03/09/4ca7qFRtHkpf1li.webp</a>&quot; alt=“zhiwu” width=“883” height=“662”&gt;<br>相信不用多说了，一图胜前言</p><h2 id="九龙岩">九龙岩</h2><img loading="lazy" src="https://odd-kilometer-351.notion.site/image/https%3A%2F%2Fs3-us-west-2.amazonaws.com%2Fsecure.notion-static.com%2Ff59e068f-8c73-4254-ad4c-f12b08a87a70%2Fjiulong.jpg?id=6c637494-c685-4bbb-be2c-c7af1a11737b&table=block&spaceId=1ecfdf7d-fd5b-4a00-9216-856a4017a8d0&width=883&height=622&userId=&cache=v2" alt="jiulong" width="883" height="662"><p>还有这里被叫做九龙岩的地方。但是从科学的角度解释的话就是火山灰和更难侵蚀的岩浆石头混在一起之后，火山灰被侵蚀掉而形成的。</p><h1 id="感想">感想</h1><p>行程最后老师说到我们要保持一颗<strong>好奇心</strong>。如果没有好奇心觉得知道了这些对我没有什么用就学不到东西的。我很赞同这种说法。的确这些知识对我日后的生活可能并不能派上用场。但是好奇心让我有了动力。这就是<a href="https://www.fengkx.top/2018/06/23/interest-first/">兴趣的力量</a>。</p><p>这趟旅程我最喜欢的便是老师引导我们进行推理，真是一门难得的好课。老师说这是最后一次开这个课了。真是很可惜啊！有幸选上这样一门绝版好课真是幸运～</p>]]></content><summary type="html">&lt;p&gt;这个学期选了一门公选叫做自然地理科考探索。课程是这样的，前四周在课室听老师讲地理知识。后面老师会组织外出考察。前四周的内容老师也是偏向后面外出考察的可选地点来讲。&lt;/p&gt;
&lt;p&gt;我报名了去西樵山的考察。原因很简单就是近。其他的路线用时间比较长，只有西樵山是可以一天来回的。不</summary><category term="旅游" scheme="https://www.fengkx.top/tags/%E6%97%85%E6%B8%B8/"/><category term="公选课" scheme="https://www.fengkx.top/tags/%E5%85%AC%E9%80%89%E8%AF%BE/"/></entry><entry><title>CSAPP Bomb Lab</title><link href="https://www.fengkx.top/post/csapp-bomb-lab/"/><id>https://www.fengkx.top/post/csapp-bomb-lab/</id><published>2019-05-08T10:53:58.000Z</published><updated>2026-03-01T16:09:07.664Z</updated><content type="html"><![CDATA[<p>大佬们都这么说<code>看 CSAPP 不做 Lab 就等于少看了一半</code>。CSAPP 的 Bomb Lab 对我来说，已经算是“久仰大名”了。今天 (2019-5-7) 就做了一下。复盘的同时也记录一下，算是检验一下自己的理解程度。</p><h1 id="准备工作">准备工作</h1><h2 id="Bomb-Lab-概览">Bomb Lab 概览</h2><p>首先要下载<code>Bomb Lab</code>地址在<a href="http://csapp.cs.cmu.edu/3e/labs.html">这里</a>选择<code>self study hanout</code>就好了。还有配套的 README 和<a href="https://scs.hosted.panopto.com/Panopto/Pages/Viewer.aspx?id=0ed08c72-f0f1-4982-aebc-92d3db7af9fd">视频</a>视频里面带着我们复习了一下汇编，还介绍了可能用用到的工具和 gdb 命令。</p><p>解压后能看到一个<code>.c</code>文件和一个可执行文件。打开<code>.c</code>文件可以看到程序的主体逻辑，有 6 个 phase 对应 6 个解密字符串。显然我们就是要逆向这个可执行文件找到这六个字符串。</p><p>还有一处很人性化的地方就是这个可执行文件提供了一个命令行参数来从文件准行读取输入。这样我们做出来的字符串可以写在一个文件里面，通过 gdb 的<code>set arg</code>命令设置从文件读取就不用重复劳动啦～</p><h2 id="gdb">gdb</h2><p>gdb 的默认功能比较简陋，当然选择<a href="https://github.com/longld/peda">peda</a>。<code>peda</code>汇编默认用<a href="https://github.com/longld/peda/issues/100"><code>intel</code>语法</a>。把下面这两行注释掉就能换成<code>CSAPP</code>里面使用的<code>AT&amp;T</code>语法了。</p><blockquote><p>756 ~ │ #self.execute(“set disassembly-flavor intel”)<br>6158 ~ │ #peda.execute(“set disassembly-flavor intel”)</p></blockquote><h2 id="objdump">objdump</h2><p><code>objdump -t bomb</code>看符号表<br><code>objdump -d bomb &gt; asm</code>返汇编重定向到文件</p><p>我们可以通过<code>b explode_bomb</code>和<code>b phase_&lt;第几关&gt;</code>在炸弹爆炸前和对应的函数处下断点。</p><h1 id="通关流程">通关流程</h1><h2 id="第一关">第一关</h2><p>gdb 进到<code>phase_1</code>这个函数看汇编可以轻松看出只要<code>eax</code>为 0 就能通关。<code>eax</code>就是<code>strings_not_equal</code>的返回值。只要字符串相等就能通关。</p><p><img loading="lazy" src="https://vip2.loli.io/2023/03/08/J8KAB5jbh7YqgMm.png" alt="phase_1_asm"></p><p>如果进到<code>strings_not_equal</code>函数看汇编代码可以看到，函数就是先比较我们输入的字符串和<code>0x402400</code>地址处的是否相同相同则继续逐位比较。完全相同则返回 0，否则返回 1。</p><p><img loading="lazy" src="https://vip2.loli.io/2023/03/08/gFKdQDExoUSTbNY.png" alt="phase_1"></p><p>答案就是<code>Border relations with Canada have never been better.</code></p><h2 id="第二关">第二关</h2><p>同样的方法在<code>phase_2</code>下断点看汇编代码</p><p><img loading="lazy" src="https://vip2.loli.io/2023/03/08/wNCraF6K3RnPdVG.png" alt="phase_2_asm"></p><p>从偏移地址第 14 处可以看出第一个数是 1</p><p>偏移地址 26 处看出后面的一个数是前一个数两倍。</p><p>答案就是<code>1 2 4 8 16 32</code></p><h2 id="第三关">第三关</h2><p><img loading="lazy" src="https://vip2.loli.io/2023/03/08/DXN1tmwd4AhQqeJ.png" alt="phase_3_asm"></p><p>看<code>phase_3</code>的汇编， <code>esi</code>被置为<code>0x4025cf</code>,然后调用了有<code>sscanf</code>的函数</p><p><img loading="lazy" src="https://vip2.loli.io/2023/03/08/ZKCGhxV38B1LDpt.png" alt="phase_3_fmt"></p><p>可以想象这个就是对应<code>scanf</code>的格式字符串，输入两个数空格分割</p><p>紧接着进行判断，判断确定输入了两个数。<br>偏移地址 34 处可以看出第一个参数要小于等于 7。然后<strong>以第一个参数为倍数</strong>，<code>0x402470</code>为基址跳转到跳转到对应位置。</p><p>对应位置会用一个立即数给<code>eax</code>赋值。最终都会跳转到偏移地址 123 处，将第二个数与<code>eax</code>也就是对应之前那个立即数比较，相等就通关了。</p><p>例如 第一个数为 2 时会跳到偏移地址 64，0x2c3 对应十进制数 707。</p><p>答案之一就是<code>2 707</code></p><h2 id="第四关">第四关</h2><p><img loading="lazy" src="https://vip2.loli.io/2023/03/08/sJW1LxAtn3hSGfQ.png" alt="phase_4_asm"></p><p>看汇编，同样的套路<code>0x4025cf</code>就是 scanf 的格式字符串，检测输入了两数。</p><p>偏移地址 39 处有一个判断，可以看出第一个数要求小于 <code>0xe(14)</code>然后调用了一个<code>func4</code>检查它的返回值<code>eax</code>要为 0。</p><p>偏移地址 69 可以看出第二个数的位置经过<code>func4</code>之后要为 0</p><p>看<code>func4</code>汇编</p><p><img loading="lazy" src="https://vip2.loli.io/2023/03/08/BGRY9ihOqMtoavN.png" alt="func4_asm"></p><p>看到有移位操作，有递归调用，分支也挺多的。咋一看看不出来，只能耐心的读懂代码</p><p>下面是等价的 c 代码</p><pre class="shiki github-light" style="background-color:#fff;color:#24292e" tabindex="0"><code class="language-c"><span class="line"></span><span class="line"><span style="color:#D73A49">int</span><span style="color:#6F42C1"> func4</span><span style="color:#24292E">(</span><span style="color:#D73A49">int</span><span style="color:#E36209"> a</span><span style="color:#24292E">,</span><span style="color:#D73A49">int</span><span style="color:#E36209"> b</span><span style="color:#24292E">, </span><span style="color:#D73A49">int</span><span style="color:#E36209"> c</span><span style="color:#24292E">, </span><span style="color:#D73A49">int</span><span style="color:#E36209"> d</span><span style="color:#24292E">) &#123;</span></span><span class="line"><span style="color:#6A737D">    /*</span></span><span class="line"><span style="color:#6A737D">    a in %edi, b in %esi, c in %edx, d in %ecx</span></span><span class="line"><span style="color:#6A737D">    r in %eax</span></span><span class="line"><span style="color:#6A737D">    */</span></span><span class="line"></span><span class="line"><span style="color:#6A737D">    // a 输入的第一个数</span></span><span class="line"><span style="color:#24292E">    c</span><span style="color:#D73A49">=0x</span><span style="color:#005CC5">e</span><span style="color:#24292E">;</span><span style="color:#6A737D"> // phase_4 调用前 func4 前初始化</span></span><span class="line"><span style="color:#24292E">    b</span><span style="color:#D73A49">=</span><span style="color:#005CC5">0</span></span><span class="line"><span style="color:#24292E">    r</span><span style="color:#D73A49">=</span><span style="color:#24292E"> c</span><span style="color:#D73A49">-</span><span style="color:#24292E">b;</span></span><span class="line"><span style="color:#24292E">    d </span><span style="color:#D73A49">=</span><span style="color:#24292E"> r;</span></span><span class="line"><span style="color:#24292E">    d</span><span style="color:#D73A49">>></span><span style="color:#005CC5">31</span><span style="color:#24292E">;</span></span><span class="line"><span style="color:#24292E">    r</span><span style="color:#D73A49">=</span><span style="color:#24292E">r</span><span style="color:#D73A49">+</span><span style="color:#24292E">d;</span></span><span class="line"><span style="color:#24292E">    r</span><span style="color:#D73A49">>></span><span style="color:#005CC5">1</span><span style="color:#24292E">;</span></span><span class="line"><span style="color:#24292E">    d</span><span style="color:#D73A49">=</span><span style="color:#24292E">r</span><span style="color:#D73A49">+</span><span style="color:#24292E">b;</span></span><span class="line"></span><span class="line"><span style="color:#D73A49">    if</span><span style="color:#24292E">(d</span><span style="color:#D73A49">&#x3C;=</span><span style="color:#24292E">a) &#123;</span></span><span class="line"><span style="color:#24292E">        r</span><span style="color:#D73A49">=</span><span style="color:#005CC5">0</span><span style="color:#24292E">;</span></span><span class="line"><span style="color:#D73A49">        if</span><span style="color:#24292E">(d</span><span style="color:#D73A49">>=</span><span style="color:#24292E">a) &#123;</span></span><span class="line"><span style="color:#D73A49">            return</span><span style="color:#24292E"> r;</span></span><span class="line"><span style="color:#24292E">        &#125; </span><span style="color:#D73A49">else</span><span style="color:#24292E"> &#123;</span></span><span class="line"><span style="color:#24292E">            b</span><span style="color:#D73A49">=</span><span style="color:#24292E">d</span><span style="color:#D73A49">+</span><span style="color:#005CC5">1</span><span style="color:#24292E">;</span></span><span class="line"><span style="color:#6F42C1">            func4</span><span style="color:#24292E">(....);</span></span><span class="line"><span style="color:#24292E">            r</span><span style="color:#D73A49">=</span><span style="color:#B31D28;font-style:italic">2r</span><span style="color:#D73A49">+</span><span style="color:#005CC5">1</span><span style="color:#24292E">;</span></span><span class="line"><span style="color:#D73A49">            return</span><span style="color:#24292E"> r;</span></span><span class="line"><span style="color:#24292E">        &#125;</span></span><span class="line"><span style="color:#24292E">    &#125; </span><span style="color:#D73A49">else</span><span style="color:#24292E"> &#123;</span></span><span class="line"><span style="color:#24292E">        c</span><span style="color:#D73A49">=</span><span style="color:#24292E">d</span><span style="color:#D73A49">-</span><span style="color:#005CC5">1</span><span style="color:#24292E">;</span></span><span class="line"><span style="color:#6F42C1">        func4</span><span style="color:#24292E">(....);</span></span><span class="line"><span style="color:#24292E">        r</span><span style="color:#D73A49">=</span><span style="color:#B31D28;font-style:italic">2r</span><span style="color:#24292E">;</span></span><span class="line"><span style="color:#D73A49">        return</span><span style="color:#24292E"> r;</span></span><span class="line"><span style="color:#24292E">    &#125;</span></span><span class="line"><span style="color:#24292E">&#125;</span></span><span class="line"></span></code></pre><p>根据已知信息，r 要为 0。而且 <code>func4</code> 没有更改输进去的第二个数。所以第二个数就是要为 0。</p><p>看 c 代码可知，使 r 为 0 的条件是<code>d==a</code>。a 就是输入的第一个数。</p><p>可以看到<code>d=((c-b) + ((c-b)&gt;&gt;31))&gt;&gt;1 + b</code>。由于 <code>b=0</code>， <code>c=0xe 0xe&gt;0</code> 所以 <code>(c-b)&gt;&gt;31 == 0</code></p><p>化简可知<code>d=c/2</code>,所以第一个数为 7。</p><p>答案<code>7 0</code></p><h2 id="第五关">第五关</h2><p><img loading="lazy" src="https://vip2.loli.io/2023/03/08/PdB4by2pMKs5txw.png" alt="phase_5_asm"></p><p>首先判断输入的字符串长度要为 6</p><p>然后进入<code>+41 - +74 </code>这个循环。</p><p>循环中取输入的字符串每个字符的 ascii 码低四位作为偏移量在 <code>0x4024b0</code>取值存入到栈 (%rsp+10)。</p><p><code>+76 - +98</code>比较生成的字符串和<code>0x40245e</code>的字符串如果相等就通关了。</p><p><img loading="lazy" src="https://vip2.loli.io/2023/03/08/R3oHldjX2zfac9S.png" alt="phase_5_str"></p><p>我们知道数字 0-9 的 ascii 码低四位与数字本身是对应的，范围是<code>0x30 ~ 0x39</code>,用 Python 的<code>chr</code>函数构造处要输入的字符串</p><p>答案<code>9?&gt;567</code></p><h2 id="第六关">第六关</h2><p>第六关 gdb 调试发现循环很多一个套一个。然后就选择直接死啃汇编。</p><pre class="shiki github-light" style="background-color:#fff;color:#24292e" tabindex="0"><code class="language-asm"><span class="line"></span><span class="line"><span style="color:#24292E">Dump of assembler code for function </span><span style="color:#6F42C1">phase_6:</span></span><span class="line"><span style="color:#24292E">=> </span><span style="color:#005CC5">0x00000000004010f4</span><span style="color:#24292E"> &#x3C;+</span><span style="color:#005CC5">0</span><span style="color:#24292E">>:</span><span style="color:#D73A49">push</span><span style="color:#24292E">   %</span><span style="color:#005CC5">r14</span></span><span class="line"><span style="color:#005CC5">   0x00000000004010f6</span><span style="color:#24292E"> &#x3C;+</span><span style="color:#005CC5">2</span><span style="color:#24292E">>:</span><span style="color:#D73A49">push</span><span style="color:#24292E">   %</span><span style="color:#005CC5">r13</span></span><span class="line"><span style="color:#005CC5">   0x00000000004010f8</span><span style="color:#24292E"> &#x3C;+</span><span style="color:#005CC5">4</span><span style="color:#24292E">>:</span><span style="color:#D73A49">push</span><span style="color:#24292E">   %</span><span style="color:#005CC5">r12</span></span><span class="line"><span style="color:#005CC5">   0x00000000004010fa</span><span style="color:#24292E"> &#x3C;+</span><span style="color:#005CC5">6</span><span style="color:#24292E">>:</span><span style="color:#D73A49">push</span><span style="color:#24292E">   %</span><span style="color:#005CC5">rbp</span></span><span class="line"><span style="color:#005CC5">   0x00000000004010fb</span><span style="color:#24292E"> &#x3C;+</span><span style="color:#005CC5">7</span><span style="color:#24292E">>:</span><span style="color:#D73A49">push</span><span style="color:#24292E">   %</span><span style="color:#005CC5">rbx</span></span><span class="line"><span style="color:#005CC5">   0x00000000004010fc</span><span style="color:#24292E"> &#x3C;+</span><span style="color:#005CC5">8</span><span style="color:#24292E">>:</span><span style="color:#D73A49">sub</span><span style="color:#24292E">    $0x50,%</span><span style="color:#005CC5">rsp</span></span><span class="line"><span style="color:#005CC5">   0x0000000000401100</span><span style="color:#24292E"> &#x3C;+</span><span style="color:#005CC5">12</span><span style="color:#24292E">>:</span><span style="color:#D73A49">mov</span><span style="color:#24292E">    %</span><span style="color:#005CC5">rsp</span><span style="color:#24292E">,%</span><span style="color:#005CC5">r13</span></span><span class="line"><span style="color:#005CC5">   0x0000000000401103</span><span style="color:#24292E"> &#x3C;+</span><span style="color:#005CC5">15</span><span style="color:#24292E">>:</span><span style="color:#D73A49">mov</span><span style="color:#24292E">    %</span><span style="color:#005CC5">rsp</span><span style="color:#24292E">,%</span><span style="color:#005CC5">rsi</span></span><span class="line"><span style="color:#005CC5">   0x0000000000401106</span><span style="color:#24292E"> &#x3C;+</span><span style="color:#005CC5">18</span><span style="color:#24292E">>:callq  </span><span style="color:#005CC5">0x40145c</span><span style="color:#24292E"> &#x3C;read_six_numbers></span></span><span class="line"><span style="color:#005CC5">   0x000000000040110b</span><span style="color:#24292E"> &#x3C;+</span><span style="color:#005CC5">23</span><span style="color:#24292E">>:</span><span style="color:#D73A49">mov</span><span style="color:#24292E">    %</span><span style="color:#005CC5">rsp</span><span style="color:#24292E">,%</span><span style="color:#005CC5">r14</span></span><span class="line"><span style="color:#005CC5">   0x000000000040110e</span><span style="color:#24292E"> &#x3C;+</span><span style="color:#005CC5">26</span><span style="color:#24292E">>:</span><span style="color:#D73A49">mov</span><span style="color:#24292E">    $0x0,%</span><span style="color:#005CC5">r12d</span></span><span class="line"><span style="color:#005CC5">   0x0000000000401114</span><span style="color:#24292E"> &#x3C;+</span><span style="color:#005CC5">32</span><span style="color:#24292E">>:</span><span style="color:#D73A49">mov</span><span style="color:#24292E">    %</span><span style="color:#005CC5">r13</span><span style="color:#24292E">,%</span><span style="color:#005CC5">rbp</span></span><span class="line"><span style="color:#005CC5">   0x0000000000401117</span><span style="color:#24292E"> &#x3C;+</span><span style="color:#005CC5">35</span><span style="color:#24292E">>:</span><span style="color:#D73A49">mov</span><span style="color:#005CC5">    0x0</span><span style="color:#24292E">(%</span><span style="color:#005CC5">r13</span><span style="color:#24292E">),%</span><span style="color:#005CC5">eax</span></span><span class="line"><span style="color:#005CC5">   0x000000000040111b</span><span style="color:#24292E"> &#x3C;+</span><span style="color:#005CC5">39</span><span style="color:#24292E">>:</span><span style="color:#D73A49">sub</span><span style="color:#24292E">    $0x1,%</span><span style="color:#005CC5">eax</span></span><span class="line"><span style="color:#005CC5">   0x000000000040111e</span><span style="color:#24292E"> &#x3C;+</span><span style="color:#005CC5">42</span><span style="color:#24292E">>:</span><span style="color:#D73A49">cmp</span><span style="color:#24292E">    $0x5,%</span><span style="color:#005CC5">eax</span></span><span class="line"><span style="color:#005CC5">   0x0000000000401121</span><span style="color:#24292E"> &#x3C;+</span><span style="color:#005CC5">45</span><span style="color:#24292E">>:</span><span style="color:#D73A49">jbe</span><span style="color:#005CC5">    0x401128</span><span style="color:#24292E"> &#x3C;phase_6+</span><span style="color:#005CC5">52</span><span style="color:#24292E">></span></span><span class="line"><span style="color:#005CC5">   0x0000000000401123</span><span style="color:#24292E"> &#x3C;+</span><span style="color:#005CC5">47</span><span style="color:#24292E">>:callq  </span><span style="color:#005CC5">0x40143a</span><span style="color:#24292E"> &#x3C;explode_bomb></span></span><span class="line"><span style="color:#005CC5">   0x0000000000401128</span><span style="color:#24292E"> &#x3C;+</span><span style="color:#005CC5">52</span><span style="color:#24292E">>:</span><span style="color:#D73A49">add</span><span style="color:#24292E">    $0x1,%</span><span style="color:#005CC5">r12d</span></span><span class="line"><span style="color:#005CC5">   0x000000000040112c</span><span style="color:#24292E"> &#x3C;+</span><span style="color:#005CC5">56</span><span style="color:#24292E">>:</span><span style="color:#D73A49">cmp</span><span style="color:#24292E">    $0x6,%</span><span style="color:#005CC5">r12d</span></span><span class="line"><span style="color:#005CC5">   0x0000000000401130</span><span style="color:#24292E"> &#x3C;+</span><span style="color:#005CC5">60</span><span style="color:#24292E">>:</span><span style="color:#D73A49">je</span><span style="color:#005CC5">     0x401153</span><span style="color:#24292E"> &#x3C;phase_6+</span><span style="color:#005CC5">95</span><span style="color:#24292E">></span></span><span class="line"><span style="color:#005CC5">   0x0000000000401132</span><span style="color:#24292E"> &#x3C;+</span><span style="color:#005CC5">62</span><span style="color:#24292E">>:</span><span style="color:#D73A49">mov</span><span style="color:#24292E">    %</span><span style="color:#005CC5">r12d</span><span style="color:#24292E">,%</span><span style="color:#005CC5">ebx</span></span><span class="line"><span style="color:#005CC5">   0x0000000000401135</span><span style="color:#24292E"> &#x3C;+</span><span style="color:#005CC5">65</span><span style="color:#24292E">>:movslq %</span><span style="color:#005CC5">ebx</span><span style="color:#24292E">,%</span><span style="color:#005CC5">rax</span></span><span class="line"><span style="color:#005CC5">   0x0000000000401138</span><span style="color:#24292E"> &#x3C;+</span><span style="color:#005CC5">68</span><span style="color:#24292E">>:</span><span style="color:#D73A49">mov</span><span style="color:#24292E">    (%</span><span style="color:#005CC5">rsp</span><span style="color:#24292E">,%</span><span style="color:#005CC5">rax</span><span style="color:#24292E">,</span><span style="color:#005CC5">4</span><span style="color:#24292E">),%</span><span style="color:#005CC5">eax</span></span><span class="line"><span style="color:#005CC5">   0x000000000040113b</span><span style="color:#24292E"> &#x3C;+</span><span style="color:#005CC5">71</span><span style="color:#24292E">>:</span><span style="color:#D73A49">cmp</span><span style="color:#24292E">    %</span><span style="color:#005CC5">eax</span><span style="color:#24292E">,</span><span style="color:#005CC5">0x0</span><span style="color:#24292E">(%</span><span style="color:#005CC5">rbp</span><span style="color:#24292E">)</span></span><span class="line"><span style="color:#005CC5">   0x000000000040113e</span><span style="color:#24292E"> &#x3C;+</span><span style="color:#005CC5">74</span><span style="color:#24292E">>:</span><span style="color:#D73A49">jne</span><span style="color:#005CC5">    0x401145</span><span style="color:#24292E"> &#x3C;phase_6+</span><span style="color:#005CC5">81</span><span style="color:#24292E">></span></span><span class="line"><span style="color:#005CC5">   0x0000000000401140</span><span style="color:#24292E"> &#x3C;+</span><span style="color:#005CC5">76</span><span style="color:#24292E">>:callq  </span><span style="color:#005CC5">0x40143a</span><span style="color:#24292E"> &#x3C;explode_bomb></span></span><span class="line"><span style="color:#005CC5">   0x0000000000401145</span><span style="color:#24292E"> &#x3C;+</span><span style="color:#005CC5">81</span><span style="color:#24292E">>:</span><span style="color:#D73A49">add</span><span style="color:#24292E">    $0x1,%</span><span style="color:#005CC5">ebx</span></span><span class="line"><span style="color:#005CC5">   0x0000000000401148</span><span style="color:#24292E"> &#x3C;+</span><span style="color:#005CC5">84</span><span style="color:#24292E">>:</span><span style="color:#D73A49">cmp</span><span style="color:#24292E">    $0x5,%</span><span style="color:#005CC5">ebx</span></span><span class="line"><span style="color:#005CC5">   0x000000000040114b</span><span style="color:#24292E"> &#x3C;+</span><span style="color:#005CC5">87</span><span style="color:#24292E">>:</span><span style="color:#D73A49">jle</span><span style="color:#005CC5">    0x401135</span><span style="color:#24292E"> &#x3C;phase_6+</span><span style="color:#005CC5">65</span><span style="color:#24292E">></span></span><span class="line"><span style="color:#005CC5">   0x000000000040114d</span><span style="color:#24292E"> &#x3C;+</span><span style="color:#005CC5">89</span><span style="color:#24292E">>:</span><span style="color:#D73A49">add</span><span style="color:#24292E">    $0x4,%</span><span style="color:#005CC5">r13</span></span><span class="line"><span style="color:#005CC5">   0x0000000000401151</span><span style="color:#24292E"> &#x3C;+</span><span style="color:#005CC5">93</span><span style="color:#24292E">>:</span><span style="color:#D73A49">jmp</span><span style="color:#005CC5">    0x401114</span><span style="color:#24292E"> &#x3C;phase_6+</span><span style="color:#005CC5">32</span><span style="color:#24292E">></span></span><span class="line"><span style="color:#005CC5">   0x0000000000401153</span><span style="color:#24292E"> &#x3C;+</span><span style="color:#005CC5">95</span><span style="color:#24292E">>:</span><span style="color:#D73A49">lea</span><span style="color:#005CC5">    0x18</span><span style="color:#24292E">(%</span><span style="color:#005CC5">rsp</span><span style="color:#24292E">),%</span><span style="color:#005CC5">rsi</span></span><span class="line"><span style="color:#005CC5">   0x0000000000401158</span><span style="color:#24292E"> &#x3C;+</span><span style="color:#005CC5">100</span><span style="color:#24292E">>:</span><span style="color:#D73A49">mov</span><span style="color:#24292E">    %</span><span style="color:#005CC5">r14</span><span style="color:#24292E">,%</span><span style="color:#005CC5">rax</span></span><span class="line"><span style="color:#005CC5">   0x000000000040115b</span><span style="color:#24292E"> &#x3C;+</span><span style="color:#005CC5">103</span><span style="color:#24292E">>:</span><span style="color:#D73A49">mov</span><span style="color:#24292E">    $0x7,%</span><span style="color:#005CC5">ecx</span></span><span class="line"><span style="color:#005CC5">   0x0000000000401160</span><span style="color:#24292E"> &#x3C;+</span><span style="color:#005CC5">108</span><span style="color:#24292E">>:</span><span style="color:#D73A49">mov</span><span style="color:#24292E">    %</span><span style="color:#005CC5">ecx</span><span style="color:#24292E">,%</span><span style="color:#005CC5">edx</span></span><span class="line"><span style="color:#005CC5">   0x0000000000401162</span><span style="color:#24292E"> &#x3C;+</span><span style="color:#005CC5">110</span><span style="color:#24292E">>:</span><span style="color:#D73A49">sub</span><span style="color:#24292E">    (%</span><span style="color:#005CC5">rax</span><span style="color:#24292E">),%</span><span style="color:#005CC5">edx</span></span><span class="line"><span style="color:#005CC5">   0x0000000000401164</span><span style="color:#24292E"> &#x3C;+</span><span style="color:#005CC5">112</span><span style="color:#24292E">>:</span><span style="color:#D73A49">mov</span><span style="color:#24292E">    %</span><span style="color:#005CC5">edx</span><span style="color:#24292E">,(%</span><span style="color:#005CC5">rax</span><span style="color:#24292E">)</span></span><span class="line"><span style="color:#005CC5">   0x0000000000401166</span><span style="color:#24292E"> &#x3C;+</span><span style="color:#005CC5">114</span><span style="color:#24292E">>:</span><span style="color:#D73A49">add</span><span style="color:#24292E">    $0x4,%</span><span style="color:#005CC5">rax</span></span><span class="line"><span style="color:#005CC5">   0x000000000040116a</span><span style="color:#24292E"> &#x3C;+</span><span style="color:#005CC5">118</span><span style="color:#24292E">>:</span><span style="color:#D73A49">cmp</span><span style="color:#24292E">    %</span><span style="color:#005CC5">rsi</span><span style="color:#24292E">,%</span><span style="color:#005CC5">rax</span></span><span class="line"><span style="color:#005CC5">   0x000000000040116d</span><span style="color:#24292E"> &#x3C;+</span><span style="color:#005CC5">121</span><span style="color:#24292E">>:</span><span style="color:#D73A49">jne</span><span style="color:#005CC5">    0x401160</span><span style="color:#24292E"> &#x3C;phase_6+</span><span style="color:#005CC5">108</span><span style="color:#24292E">></span></span><span class="line"><span style="color:#005CC5">   0x000000000040116f</span><span style="color:#24292E"> &#x3C;+</span><span style="color:#005CC5">123</span><span style="color:#24292E">>:</span><span style="color:#D73A49">mov</span><span style="color:#24292E">    $0x0,%</span><span style="color:#005CC5">esi</span></span><span class="line"><span style="color:#005CC5">   0x0000000000401174</span><span style="color:#24292E"> &#x3C;+</span><span style="color:#005CC5">128</span><span style="color:#24292E">>:</span><span style="color:#D73A49">jmp</span><span style="color:#005CC5">    0x401197</span><span style="color:#24292E"> &#x3C;phase_6+</span><span style="color:#005CC5">163</span><span style="color:#24292E">></span></span><span class="line"><span style="color:#005CC5">   0x0000000000401176</span><span style="color:#24292E"> &#x3C;+</span><span style="color:#005CC5">130</span><span style="color:#24292E">>:</span><span style="color:#D73A49">mov</span><span style="color:#005CC5">    0x8</span><span style="color:#24292E">(%</span><span style="color:#005CC5">rdx</span><span style="color:#24292E">),%</span><span style="color:#005CC5">rdx</span></span><span class="line"><span style="color:#005CC5">   0x000000000040117a</span><span style="color:#24292E"> &#x3C;+</span><span style="color:#005CC5">134</span><span style="color:#24292E">>:</span><span style="color:#D73A49">add</span><span style="color:#24292E">    $0x1,%</span><span style="color:#005CC5">eax</span></span><span class="line"><span style="color:#005CC5">   0x000000000040117d</span><span style="color:#24292E"> &#x3C;+</span><span style="color:#005CC5">137</span><span style="color:#24292E">>:</span><span style="color:#D73A49">cmp</span><span style="color:#24292E">    %</span><span style="color:#005CC5">ecx</span><span style="color:#24292E">,%</span><span style="color:#005CC5">eax</span></span><span class="line"><span style="color:#005CC5">   0x000000000040117f</span><span style="color:#24292E"> &#x3C;+</span><span style="color:#005CC5">139</span><span style="color:#24292E">>:</span><span style="color:#D73A49">jne</span><span style="color:#005CC5">    0x401176</span><span style="color:#24292E"> &#x3C;phase_6+</span><span style="color:#005CC5">130</span><span style="color:#24292E">></span></span><span class="line"><span style="color:#005CC5">   0x0000000000401181</span><span style="color:#24292E"> &#x3C;+</span><span style="color:#005CC5">141</span><span style="color:#24292E">>:</span><span style="color:#D73A49">jmp</span><span style="color:#005CC5">    0x401188</span><span style="color:#24292E"> &#x3C;phase_6+</span><span style="color:#005CC5">148</span><span style="color:#24292E">></span></span><span class="line"><span style="color:#005CC5">   0x0000000000401183</span><span style="color:#24292E"> &#x3C;+</span><span style="color:#005CC5">143</span><span style="color:#24292E">>:</span><span style="color:#D73A49">mov</span><span style="color:#24292E">    $0x6032d0,%</span><span style="color:#005CC5">edx</span></span><span class="line"><span style="color:#005CC5">   0x0000000000401188</span><span style="color:#24292E"> &#x3C;+</span><span style="color:#005CC5">148</span><span style="color:#24292E">>:</span><span style="color:#D73A49">mov</span><span style="color:#24292E">    %</span><span style="color:#005CC5">rdx</span><span style="color:#24292E">,</span><span style="color:#005CC5">0x20</span><span style="color:#24292E">(%</span><span style="color:#005CC5">rsp</span><span style="color:#24292E">,%</span><span style="color:#005CC5">rsi</span><span style="color:#24292E">,</span><span style="color:#005CC5">2</span><span style="color:#24292E">)</span></span><span class="line"><span style="color:#005CC5">   0x000000000040118d</span><span style="color:#24292E"> &#x3C;+</span><span style="color:#005CC5">153</span><span style="color:#24292E">>:</span><span style="color:#D73A49">add</span><span style="color:#24292E">    $0x4,%</span><span style="color:#005CC5">rsi</span></span><span class="line"><span style="color:#005CC5">   0x0000000000401191</span><span style="color:#24292E"> &#x3C;+</span><span style="color:#005CC5">157</span><span style="color:#24292E">>:</span><span style="color:#D73A49">cmp</span><span style="color:#24292E">    $0x18,%</span><span style="color:#005CC5">rsi</span></span><span class="line"><span style="color:#005CC5">   0x0000000000401195</span><span style="color:#24292E"> &#x3C;+</span><span style="color:#005CC5">161</span><span style="color:#24292E">>:</span><span style="color:#D73A49">je</span><span style="color:#005CC5">     0x4011ab</span><span style="color:#24292E"> &#x3C;phase_6+</span><span style="color:#005CC5">183</span><span style="color:#24292E">></span></span><span class="line"><span style="color:#005CC5">   0x0000000000401197</span><span style="color:#24292E"> &#x3C;+</span><span style="color:#005CC5">163</span><span style="color:#24292E">>:</span><span style="color:#D73A49">mov</span><span style="color:#24292E">    (%</span><span style="color:#005CC5">rsp</span><span style="color:#24292E">,%</span><span style="color:#005CC5">rsi</span><span style="color:#24292E">,</span><span style="color:#005CC5">1</span><span style="color:#24292E">),%</span><span style="color:#005CC5">ecx</span></span><span class="line"><span style="color:#005CC5">   0x000000000040119a</span><span style="color:#24292E"> &#x3C;+</span><span style="color:#005CC5">166</span><span style="color:#24292E">>:</span><span style="color:#D73A49">cmp</span><span style="color:#24292E">    $0x1,%</span><span style="color:#005CC5">ecx</span></span><span class="line"><span style="color:#005CC5">   0x000000000040119d</span><span style="color:#24292E"> &#x3C;+</span><span style="color:#005CC5">169</span><span style="color:#24292E">>:</span><span style="color:#D73A49">jle</span><span style="color:#005CC5">    0x401183</span><span style="color:#24292E"> &#x3C;phase_6+</span><span style="color:#005CC5">143</span><span style="color:#24292E">></span></span><span class="line"><span style="color:#005CC5">   0x000000000040119f</span><span style="color:#24292E"> &#x3C;+</span><span style="color:#005CC5">171</span><span style="color:#24292E">>:</span><span style="color:#D73A49">mov</span><span style="color:#24292E">    $0x1,%</span><span style="color:#005CC5">eax</span></span><span class="line"><span style="color:#005CC5">   0x00000000004011a4</span><span style="color:#24292E"> &#x3C;+</span><span style="color:#005CC5">176</span><span style="color:#24292E">>:</span><span style="color:#D73A49">mov</span><span style="color:#24292E">    $0x6032d0,%</span><span style="color:#005CC5">edx</span></span><span class="line"><span style="color:#005CC5">   0x00000000004011a9</span><span style="color:#24292E"> &#x3C;+</span><span style="color:#005CC5">181</span><span style="color:#24292E">>:</span><span style="color:#D73A49">jmp</span><span style="color:#005CC5">    0x401176</span><span style="color:#24292E"> &#x3C;phase_6+</span><span style="color:#005CC5">130</span><span style="color:#24292E">></span></span><span class="line"><span style="color:#005CC5">   0x00000000004011ab</span><span style="color:#24292E"> &#x3C;+</span><span style="color:#005CC5">183</span><span style="color:#24292E">>:</span><span style="color:#D73A49">mov</span><span style="color:#005CC5">    0x20</span><span style="color:#24292E">(%</span><span style="color:#005CC5">rsp</span><span style="color:#24292E">),%</span><span style="color:#005CC5">rbx</span></span><span class="line"><span style="color:#005CC5">   0x00000000004011b0</span><span style="color:#24292E"> &#x3C;+</span><span style="color:#005CC5">188</span><span style="color:#24292E">>:</span><span style="color:#D73A49">lea</span><span style="color:#005CC5">    0x28</span><span style="color:#24292E">(%</span><span style="color:#005CC5">rsp</span><span style="color:#24292E">),%</span><span style="color:#005CC5">rax</span></span><span class="line"><span style="color:#005CC5">   0x00000000004011b5</span><span style="color:#24292E"> &#x3C;+</span><span style="color:#005CC5">193</span><span style="color:#24292E">>:</span><span style="color:#D73A49">lea</span><span style="color:#005CC5">    0x50</span><span style="color:#24292E">(%</span><span style="color:#005CC5">rsp</span><span style="color:#24292E">),%</span><span style="color:#005CC5">rsi</span></span><span class="line"><span style="color:#005CC5">   0x00000000004011ba</span><span style="color:#24292E"> &#x3C;+</span><span style="color:#005CC5">198</span><span style="color:#24292E">>:</span><span style="color:#D73A49">mov</span><span style="color:#24292E">    %</span><span style="color:#005CC5">rbx</span><span style="color:#24292E">,%</span><span style="color:#005CC5">rcx</span></span><span class="line"><span style="color:#005CC5">   0x00000000004011bd</span><span style="color:#24292E"> &#x3C;+</span><span style="color:#005CC5">201</span><span style="color:#24292E">>:</span><span style="color:#D73A49">mov</span><span style="color:#24292E">    (%</span><span style="color:#005CC5">rax</span><span style="color:#24292E">),%</span><span style="color:#005CC5">rdx</span></span><span class="line"><span style="color:#005CC5">   0x00000000004011c0</span><span style="color:#24292E"> &#x3C;+</span><span style="color:#005CC5">204</span><span style="color:#24292E">>:</span><span style="color:#D73A49">mov</span><span style="color:#24292E">    %</span><span style="color:#005CC5">rdx</span><span style="color:#24292E">,</span><span style="color:#005CC5">0x8</span><span style="color:#24292E">(%</span><span style="color:#005CC5">rcx</span><span style="color:#24292E">)</span></span><span class="line"><span style="color:#005CC5">   0x00000000004011c4</span><span style="color:#24292E"> &#x3C;+</span><span style="color:#005CC5">208</span><span style="color:#24292E">>:</span><span style="color:#D73A49">add</span><span style="color:#24292E">    $0x8,%</span><span style="color:#005CC5">rax</span></span><span class="line"><span style="color:#005CC5">   0x00000000004011c8</span><span style="color:#24292E"> &#x3C;+</span><span style="color:#005CC5">212</span><span style="color:#24292E">>:</span><span style="color:#D73A49">cmp</span><span style="color:#24292E">    %</span><span style="color:#005CC5">rsi</span><span style="color:#24292E">,%</span><span style="color:#005CC5">rax</span></span><span class="line"><span style="color:#005CC5">   0x00000000004011cb</span><span style="color:#24292E"> &#x3C;+</span><span style="color:#005CC5">215</span><span style="color:#24292E">>:</span><span style="color:#D73A49">je</span><span style="color:#005CC5">     0x4011d2</span><span style="color:#24292E"> &#x3C;phase_6+</span><span style="color:#005CC5">222</span><span style="color:#24292E">></span></span><span class="line"><span style="color:#005CC5">   0x00000000004011cd</span><span style="color:#24292E"> &#x3C;+</span><span style="color:#005CC5">217</span><span style="color:#24292E">>:</span><span style="color:#D73A49">mov</span><span style="color:#24292E">    %</span><span style="color:#005CC5">rdx</span><span style="color:#24292E">,%</span><span style="color:#005CC5">rcx</span></span><span class="line"><span style="color:#005CC5">   0x00000000004011d0</span><span style="color:#24292E"> &#x3C;+</span><span style="color:#005CC5">220</span><span style="color:#24292E">>:</span><span style="color:#D73A49">jmp</span><span style="color:#005CC5">    0x4011bd</span><span style="color:#24292E"> &#x3C;phase_6+</span><span style="color:#005CC5">201</span><span style="color:#24292E">></span></span><span class="line"><span style="color:#005CC5">   0x00000000004011d2</span><span style="color:#24292E"> &#x3C;+</span><span style="color:#005CC5">222</span><span style="color:#24292E">>:</span><span style="color:#D73A49">movq</span><span style="color:#24292E">   $0x0,</span><span style="color:#005CC5">0x8</span><span style="color:#24292E">(%</span><span style="color:#005CC5">rdx</span><span style="color:#24292E">)</span></span><span class="line"><span style="color:#005CC5">   0x00000000004011da</span><span style="color:#24292E"> &#x3C;+</span><span style="color:#005CC5">230</span><span style="color:#24292E">>:</span><span style="color:#D73A49">mov</span><span style="color:#24292E">    $0x5,%</span><span style="color:#005CC5">ebp</span></span><span class="line"><span style="color:#005CC5">   0x00000000004011df</span><span style="color:#24292E"> &#x3C;+</span><span style="color:#005CC5">235</span><span style="color:#24292E">>:</span><span style="color:#D73A49">mov</span><span style="color:#005CC5">    0x8</span><span style="color:#24292E">(%</span><span style="color:#005CC5">rbx</span><span style="color:#24292E">),%</span><span style="color:#005CC5">rax</span></span><span class="line"><span style="color:#005CC5">   0x00000000004011e3</span><span style="color:#24292E"> &#x3C;+</span><span style="color:#005CC5">239</span><span style="color:#24292E">>:</span><span style="color:#D73A49">mov</span><span style="color:#24292E">    (%</span><span style="color:#005CC5">rax</span><span style="color:#24292E">),%</span><span style="color:#005CC5">eax</span></span><span class="line"><span style="color:#005CC5">   0x00000000004011e5</span><span style="color:#24292E"> &#x3C;+</span><span style="color:#005CC5">241</span><span style="color:#24292E">>:</span><span style="color:#D73A49">cmp</span><span style="color:#24292E">    %</span><span style="color:#005CC5">eax</span><span style="color:#24292E">,(%</span><span style="color:#005CC5">rbx</span><span style="color:#24292E">)</span></span><span class="line"><span style="color:#005CC5">   0x00000000004011e7</span><span style="color:#24292E"> &#x3C;+</span><span style="color:#005CC5">243</span><span style="color:#24292E">>:</span><span style="color:#D73A49">jge</span><span style="color:#005CC5">    0x4011ee</span><span style="color:#24292E"> &#x3C;phase_6+</span><span style="color:#005CC5">250</span><span style="color:#24292E">></span></span><span class="line"><span style="color:#005CC5">   0x00000000004011e9</span><span style="color:#24292E"> &#x3C;+</span><span style="color:#005CC5">245</span><span style="color:#24292E">>:callq  </span><span style="color:#005CC5">0x40143a</span><span style="color:#24292E"> &#x3C;explode_bomb></span></span><span class="line"><span style="color:#005CC5">   0x00000000004011ee</span><span style="color:#24292E"> &#x3C;+</span><span style="color:#005CC5">250</span><span style="color:#24292E">>:</span><span style="color:#D73A49">mov</span><span style="color:#005CC5">    0x8</span><span style="color:#24292E">(%</span><span style="color:#005CC5">rbx</span><span style="color:#24292E">),%</span><span style="color:#005CC5">rbx</span></span><span class="line"><span style="color:#005CC5">   0x00000000004011f2</span><span style="color:#24292E"> &#x3C;+</span><span style="color:#005CC5">254</span><span style="color:#24292E">>:</span><span style="color:#D73A49">sub</span><span style="color:#24292E">    $0x1,%</span><span style="color:#005CC5">ebp</span></span><span class="line"><span style="color:#005CC5">   0x00000000004011f5</span><span style="color:#24292E"> &#x3C;+</span><span style="color:#005CC5">257</span><span style="color:#24292E">>:</span><span style="color:#D73A49">jne</span><span style="color:#005CC5">    0x4011df</span><span style="color:#24292E"> &#x3C;phase_6+</span><span style="color:#005CC5">235</span><span style="color:#24292E">></span></span><span class="line"><span style="color:#005CC5">   0x00000000004011f7</span><span style="color:#24292E"> &#x3C;+</span><span style="color:#005CC5">259</span><span style="color:#24292E">>:</span><span style="color:#D73A49">add</span><span style="color:#24292E">    $0x50,%</span><span style="color:#005CC5">rsp</span></span><span class="line"><span style="color:#005CC5">   0x00000000004011fb</span><span style="color:#24292E"> &#x3C;+</span><span style="color:#005CC5">263</span><span style="color:#24292E">>:</span><span style="color:#D73A49">pop</span><span style="color:#24292E">    %</span><span style="color:#005CC5">rbx</span></span><span class="line"><span style="color:#005CC5">   0x00000000004011fc</span><span style="color:#24292E"> &#x3C;+</span><span style="color:#005CC5">264</span><span style="color:#24292E">>:</span><span style="color:#D73A49">pop</span><span style="color:#24292E">    %</span><span style="color:#005CC5">rbp</span></span><span class="line"><span style="color:#005CC5">   0x00000000004011fd</span><span style="color:#24292E"> &#x3C;+</span><span style="color:#005CC5">265</span><span style="color:#24292E">>:</span><span style="color:#D73A49">pop</span><span style="color:#24292E">    %</span><span style="color:#005CC5">r12</span></span><span class="line"><span style="color:#005CC5">   0x00000000004011ff</span><span style="color:#24292E"> &#x3C;+</span><span style="color:#005CC5">267</span><span style="color:#24292E">>:</span><span style="color:#D73A49">pop</span><span style="color:#24292E">    %</span><span style="color:#005CC5">r13</span></span><span class="line"><span style="color:#005CC5">   0x0000000000401201</span><span style="color:#24292E"> &#x3C;+</span><span style="color:#005CC5">269</span><span style="color:#24292E">>:</span><span style="color:#D73A49">pop</span><span style="color:#24292E">    %</span><span style="color:#005CC5">r14</span></span><span class="line"><span style="color:#005CC5">   0x0000000000401203</span><span style="color:#24292E"> &#x3C;+</span><span style="color:#005CC5">271</span><span style="color:#24292E">>:retq</span></span><span class="line"><span style="color:#24292E">End of assembler dump.</span></span></code></pre><p><code>+32 - +93</code> 和 <code>+65 - +87</code> 是一个两层的嵌套循环。就是判断每个数要不同而且小于等于 6 且 不等于 0，否则炸弹都会爆炸</p><p><code>+108 - +121</code> 循环把输入的每个数都用 7 减去这个数。也就是 <code>a[i] = 7 - a[i]</code></p><p>后面的汇编看了好久也看不出个所以然。于是<sub>～无耻的</sub>～打开了 ida 按下了 F5</p><p><img loading="lazy" src="https://vip2.loli.io/2023/03/08/txSIBuAJL6glpUv.png" alt="phase_6_ida_1"></p><p>ida 看 phase_6 印证了前两个循环<br>可以看到 node 这个东西，点进去看到数据段</p><p><img loading="lazy" src="https://vip2.loli.io/2023/03/08/5hAzld2WejbsnEV.png" alt="phase_6_ida_2"></p><p>node 一共有 6 个 每个 16 byte</p><p><img loading="lazy" src="https://vip2.loli.io/2023/03/08/qinhT6lWPsYCOBt.png" alt="phase_6_x24w"></p><p>gdb 查看字节，可以更直观的看到这是个链表 每个 node 第三个四字指向下一个 node。</p><p><img loading="lazy" src="https://vip2.loli.io/2023/03/08/PW3fUp8tHYbODFu.png" alt="phase_6_ida_3"></p><p><img loading="lazy" src="https://vip2.loli.io/2023/03/08/k2A8YKvFhTmel97.png" alt="phase_6_ida_4"></p><p>伪代码 53 行的 do-while 的意思是按照输入的数字取第 n 个 node 的数值 (int64) 放入栈中</p><p>然后每个数都加 8，要求前一个数大于后一个数则通关。</p><p>我们可以从 gdb 看到数值，手工排下序得到<code>3 4 5 6 1 2</code></p><p>但是我们输入的数字之前都被用 7 减过，所以答案是 <code>4 3 2 1 6 5</code></p><p>IDA 大法好！</p><h1 id="总结">总结</h1><p><code>bomb lab</code>真好玩。</p><p>看汇编的时候要整体来看，有时候抓住关键的跳转语句，找到循环会好很多。</p><p>好像还有隐藏关卡，先留个坑</p>]]></content><summary type="html">&lt;p&gt;大佬们都这么说&lt;code&gt;看 CSAPP 不做 Lab 就等于少看了一半&lt;/code&gt;。CSAPP 的 Bomb Lab 对我来说，已经算是“久仰大名”了。今天 (2019-5-7) 就做了一下。复盘的同时也记录一下，算是检验一下自己的理解程度。&lt;/p&gt;
&lt;h1 id=&quot;准</summary><category term="CSAPP" scheme="https://www.fengkx.top/tags/CSAPP/"/><category term="Bomb Lab" scheme="https://www.fengkx.top/tags/Bomb-Lab/"/><category term="逆向" scheme="https://www.fengkx.top/tags/%E9%80%86%E5%90%91/"/></entry><entry><title>axios https 走 http proxy 的坑</title><link href="https://www.fengkx.top/post/fix-https-over-http-proxy-in-axios/"/><id>https://www.fengkx.top/post/fix-https-over-http-proxy-in-axios/</id><published>2018-10-31T20:20:00.000Z</published><updated>2026-03-01T16:09:07.664Z</updated><content type="html"><![CDATA[<p>自从写 JavaScript 之后越来越觉得它真的很好玩。异步，Promise，回调函数。各种各样的神奇姿势～</p><span id="more"></span><p>最近，<s>两个月前开始的，中间鸽了很久。博客也没更很久了</s>打算以社团的名义折腾一个 RSS 的网站放到学校的服务器里。网站的其中一个部分是用<a href="https://rsshub.app">RSSHub</a> ,请求外部网站生成 RSS 订阅源。学校的服务器要通过 Http Proxy 才能访问外网。RSSHub 用的是 axios。支持 Promise 兼容浏览器 XMLHttpRequest 和 node 的 http 请求。还有拦截器等各种操作，很爽。但是 http proxy 就出问题了。</p><h1 id="axios-中的-HTTP-Proxy">axios 中的 HTTP Proxy</h1><p>axios 本身支持 http proxy</p><pre class="shiki github-light" style="background-color:#fff;color:#24292e" tabindex="0"><code class="language-js"><span class="line"><span style="color:#6F42C1">  proxy</span><span style="color:#24292E">: &#123;</span></span><span class="line"><span style="color:#6F42C1">    host</span><span style="color:#24292E">: </span><span style="color:#032F62">'127.0.0.1'</span><span style="color:#24292E">,</span></span><span class="line"><span style="color:#6F42C1">    port</span><span style="color:#24292E">: </span><span style="color:#005CC5">9000</span><span style="color:#24292E">,</span></span><span class="line"><span style="color:#6F42C1">    auth</span><span style="color:#24292E">: &#123;</span></span><span class="line"><span style="color:#6F42C1">      username</span><span style="color:#24292E">: </span><span style="color:#032F62">'mikeymike'</span><span style="color:#24292E">,</span></span><span class="line"><span style="color:#6F42C1">      password</span><span style="color:#24292E">: </span><span style="color:#032F62">'rapunz3l'</span></span><span class="line"><span style="color:#24292E">    &#125;</span></span></code></pre><p>本来是很简单很舒服。但是访问 https 网站是就会出现类似这样的东西</p><pre class="shiki github-light" style="background-color:#fff;color:#24292e" tabindex="0"><code class="language-text"><span class="line"><span>Error: write EPROTO 140736379442112:error:140770FC:SSL routines:SSL23_GET_SERVER_HELLO:unknown protocol:../deps/openssl/openssl/ssl/s23_clnt.c:794</span></span></code></pre><h1 id="解决问题">解决问题</h1><p>node 不行，但是服务器命令行中用<code>curl</code>访问 https 网站没有问题。<br>Google 后发现是 axios 的问题，而且已经<a href="https://github.com/axios/axios/pull/959">有修复了 Pre-release(v0.19.0-beta.1)</a><br><img loading="lazy" src="https://vip2.loli.io/2023/03/08/AwksRTglBWm1Y7Z.png" alt="axios pre-release"><br>然而，安了新版之后问题并没有解决，反而多了很多的 501 错误<br>网络世界那么大，我果然不是第一个遇到问题的人。用<a href="https://www.npmjs.com/package/axios-https-proxy-fix">axios-https-proxy-fix</a>问题就解决了。<br>看了一下<a href="https://github.com/Sitronik/axios/commit/36c1f46cb3e39b620bf81b5ba4069f1ddd02d4ce">源代码改动</a>发现用了 HttpsProxyAgent 包的实例 agent 替换了 config 里面的 httpAgent。config 就是 axios 做第二个参数的对象。<br>这个包没更新到最新的 axios，强迫症的我受不了。于是就模仿这个思路放在了 axios 的拦截器中。</p><pre class="shiki github-light" style="background-color:#fff;color:#24292e" tabindex="0"><code class="language-js"><span class="line"><span style="color:#D73A49">const</span><span style="color:#005CC5"> axios</span><span style="color:#D73A49"> =</span><span style="color:#6F42C1"> require</span><span style="color:#24292E">(</span><span style="color:#032F62">"axios"</span><span style="color:#24292E">);</span></span><span class="line"><span style="color:#D73A49">const</span><span style="color:#005CC5"> tunnel</span><span style="color:#D73A49"> =</span><span style="color:#6F42C1"> require</span><span style="color:#24292E">(</span><span style="color:#032F62">"tunnel"</span><span style="color:#24292E">);</span></span><span class="line"></span><span class="line"><span style="color:#D73A49">const</span><span style="color:#005CC5"> agent</span><span style="color:#D73A49"> =</span><span style="color:#24292E"> tunnel.</span><span style="color:#6F42C1">httpsOverHttp</span><span style="color:#24292E">(&#123; proxy: config.proxy &#125;);</span></span><span class="line"><span style="color:#24292E">axios.interceptors.request.</span><span style="color:#6F42C1">use</span><span style="color:#24292E">(</span><span style="color:#D73A49">function</span><span style="color:#24292E"> (</span><span style="color:#E36209">config</span><span style="color:#24292E">) &#123;</span></span><span class="line"><span style="color:#D73A49">  if</span><span style="color:#24292E"> (config.url.</span><span style="color:#6F42C1">indexOf</span><span style="color:#24292E">(</span><span style="color:#032F62">"https"</span><span style="color:#24292E">) </span><span style="color:#D73A49">!==</span><span style="color:#D73A49"> -</span><span style="color:#005CC5">1</span><span style="color:#24292E">) &#123;</span></span><span class="line"><span style="color:#24292E">    config.httpsAgent </span><span style="color:#D73A49">=</span><span style="color:#24292E"> agent;</span></span><span class="line"><span style="color:#24292E">    config.proxy </span><span style="color:#D73A49">=</span><span style="color:#005CC5"> false</span><span style="color:#24292E">;</span></span><span class="line"><span style="color:#24292E">  &#125;</span></span><span class="line"><span style="color:#D73A49">  return</span><span style="color:#24292E"> config;</span></span><span class="line"><span style="color:#24292E">&#125;);</span></span></code></pre><p><code>tunnel</code>和<code>HttpsProxyAgent</code>用法一样但是不用自己手动构造 url。这样就可以满足我追更新的强迫症了。</p>]]></content><summary type="html">&lt;p&gt;自从写 JavaScript 之后越来越觉得它真的很好玩。异步，Promise，回调函数。各种各样的神奇姿势～&lt;/p&gt;</summary><category term="JavaScript" scheme="https://www.fengkx.top/tags/JavaScript/"/><category term="axios" scheme="https://www.fengkx.top/tags/axios/"/></entry><entry><title>蓝牙指点杆键盘体验</title><link href="https://www.fengkx.top/post/bluetooth-keyboard-with-trackpoint/"/><id>https://www.fengkx.top/post/bluetooth-keyboard-with-trackpoint/</id><published>2018-07-11T10:49:30.000Z</published><updated>2026-03-01T16:09:07.664Z</updated><content type="html"><![CDATA[<p>作为计算机的学生，未来的 IT 精英 (误 身边的很多同学都买了 (机械) 键盘。毕竟是营生和学习的工具，花大价钱买一个也是值得的。但是机械键盘试了一下之后觉得并不适合我。如果你也有同感，不妨考虑一下这篇文章的主角—— thinkpad 蓝牙<strong>指点杆</strong>键盘</p><h1 id="机械键盘的不爽之处">机械键盘的不爽之处</h1><p>之前试的那个键盘是以手感偏软著称的红轴，但是用惯了笔记本自带巧克力键盘的我还是觉得太硬了。而且不知道为什么，我老是容易一次按到两个键，这点在巧克力键盘上没有出现过。</p><p>最重要的是，我也不是什么用 Vim 的大佬，平时都是 VSCode 或者 JetBrains 全家桶。虽然能用快捷键一般都用，但是鼠标和功能键都不能少啊。用笔记本键盘时，一般拇指可以用上触摸板，手也不用离开键盘。但是如果外接机械键盘的话手还要离开键盘才能用鼠标或者触摸板。真的蛋疼。</p><h1 id="适合我的——-thinkpad-指点杆键盘">适合我的—— thinkpad 指点杆键盘</h1><p>有了指点杆（还不太习惯）但是双手可以保持原来的姿势，非常适合平时都用快捷键，但有时候用一下鼠标<strong>而且穷</strong>的我。我选择了一款蓝牙单模（只能连蓝牙不能接线）的 Thinkpad 0B47189<br><img loading="lazy" src="https://vip2.loli.io/2023/03/07/B91ET6xKtyGhVvr.jpg" width="883" height="662" alt="keyboard"><br>这个键盘，本来设计的使用场景就是带出去用或者接手机 iPad 的，所以很薄便携，用的巧克力键盘。经典 Thinkpad 红黑配色，做工质量不错。价格相比于机械键盘挺便宜了。不怕吵到可爱的舍友们，手不用离开键盘就可以操作鼠标了。总体很满意。<br><strong>而且还有 thinkpad 信仰加成</strong><br><img src="https://vip2.loli.io/2023/03/08/hexai6mYBZc3NJp.webp" width="883" height="662" alt="logo"></p><h2 id="不满意的地方">不满意的地方</h2><ol><li>键盘键程没有比笔记本的大多少，TrackPoint 的中键总有中没有按下去的感觉。如果 Thinkpad 肯出带小红点的旧式键盘（就是旧版 Thinkpad 上的）那肯定的剁手啊</li><li>Thinkpad 的键位还很不习惯。是的说的就是那个 ctrl 和 Fn</li><li>单模蓝牙键盘不连蓝牙不能用，也就意味着你不能用它来输开机密码（双模的要贵两百，谁让我穷呢）</li><li>这种样式的巧克力键盘，有很大的缝容易藏污纳垢（下图）</li></ol><img loading="lazy" src="https://vip2.loli.io/2023/03/08/rEe7zCYdu9wnUjM.webp" alt="缝隙" width="883" height="662" ><h1 id="双系统的那些坑">双系统的那些坑</h1><p>刚买回来顺利接上了 Deepin 没什么问题。然后切到 Windows 装了驱动然后就不能在 Linux 连上了。<a href="https://blog.csdn.net/CaptainArcher/article/details/41379885">然后找到了解决方案</a><br>其实就是键盘连接要配对码，还要验证一个 Key 是否对应。但是因为两个系统是同一台机，MAC 地址相同的，所以不能用两个不同的 Key。而 Linux 对这方面的支持不够好，已经有了一个 key 的设备不会重新配对，还是用回旧的 Key 然后就连不上了。只需要按部就班把 Windows 的 key 搞到 Linux 用就好。多说一句 Linux 里面原有的 Key 字母全都是大写的，为避免出现奇怪的问题。如果当初用的小写字母，就到 Python shell 里<code>.upper()</code>就好了。现在大部分发行版都自带 Python 了。</p><p>什么时候能练就<a href="https://v.youku.com/v_show/id_XODQzNTU2MzU2">小红点打 CS 的神功</a>呢？</p><p>EOF</p>]]></content><summary type="html">&lt;p&gt;作为计算机的学生，未来的 IT 精英 (误 身边的很多同学都买了 (机械) 键盘。毕竟是营生和学习的工具，花大价钱买一个也是值得的。但是机械键盘试了一下之后觉得并不适合我。如果你也有同感，不妨考虑一下这篇文章的主角—— thinkpad 蓝牙&lt;strong&gt;指点杆&lt;/str</summary><category term="键盘" scheme="https://www.fengkx.top/tags/%E9%94%AE%E7%9B%98/"/><category term="体验" scheme="https://www.fengkx.top/tags/%E4%BD%93%E9%AA%8C/"/><category term="硬件" scheme="https://www.fengkx.top/tags/%E7%A1%AC%E4%BB%B6/"/></entry><entry><title>评论系统折腾录</title><link href="https://www.fengkx.top/post/comment-system/"/><id>https://www.fengkx.top/post/comment-system/</id><published>2018-06-23T21:44:34.000Z</published><updated>2026-03-01T16:09:07.664Z</updated><content type="html"><![CDATA[<p>不像动态博客一般带有评论系统，静态博客的评论只能借助第三方。刚开始搭这个博客的时候，传言中的多说已经死了，用的是友言。当时看它的<a href="http://uyan.cc">网站</a>还有评价，感觉快要凉了。<a href="https://archive.fo/Yl9TD">点击此处一睹芳容</a>谁知道就真的凉了，现在网站已经上不了。尝试了<a href="https://disqus.com">Disqus</a> 本来是挺好的。可惜因为墙的问题，经常打不开。打不开就算了，托慢了网站的加载速度，SEO 会翻车的。虽然说不怎么在乎搜索排名，但是我还是有些强迫症。改用了<a href="https://www.hypercomments.com/">HyperComments</a>。</p><p><img loading="lazy" src="https://vip2.loli.io/2023/03/08/eYhiylF86dMq4QS.png" alt="hp-comment"><br><code>没交钱多了个 close discussion 后的样式</code></p><p>免费，样式和 Next 主题还挺搭。来自俄国，简洁且支持游客评论。虽说加载比较慢 (托慢了整体网站的速度)，用着还是很舒适的。可是 HyperComments 于 2018 年 06 月 15 日不再提供免费服务。</p><blockquote><p>Уважаемые пользователи HyperComments,<br>Как ранее сообщалось в email-уведомлении от 14.05.2018, мы вынуждены прекратить поддержку бесплатного тарифного плана.</p><p>Начиная с 15.06.2018 тарифный пакет «Lite» не доступен.<br>Для продолжения работы с HyperComments выберите один из платных тарифных планов ниже.<br>Если Вы не успели выгрузить свои комментарии, Вы можете сделать это с помощью инструкции.</p></blockquote><p>付费最少要 24 刀一年，又翻车了。于是找到了<a href="https://valine.js.org/">Valine</a></p><blockquote><p>Valine 是一款基于 Leancloud 的快速、简洁且高效的无后端评论系统。</p></blockquote><p><strong>评论数据在 Leancloud</strong>，加载神速，很爽。下面将会进行测试。</p><p><strong>其实折腾这么多，评论系统还不是只有我自己在用</strong>好吧，到现在为止除了我自己的有过 3 条，现在这么一迁又会没了 (HyperComments 现在连评论管理页都进不去)</p><p>对 Next 主题做了些改动，想要换用<a href="https://valine.js.org/">Valine</a> 可以把下面的这几段代码直接替换相应文件，然后在主题的配置文件加入以下字段就搞定。</p><pre class="shiki github-light" style="background-color:#fff;color:#24292e" tabindex="0"><code class="language-yaml"><span class="line"><span style="color:#6A737D"># Valine</span></span><span class="line"><span style="color:#22863A">valine</span><span style="color:#24292E">:</span></span><span class="line"><span style="color:#22863A">  enable</span><span style="color:#24292E">: </span><span style="color:#005CC5">true</span><span style="color:#6A737D"> #true or false</span></span><span class="line"><span style="color:#22863A">  appid</span><span style="color:#24292E">: </span><span style="color:#032F62">appid 获取方式 https://valine.js.org/quickstart/</span></span><span class="line"><span style="color:#22863A">  appkey</span><span style="color:#24292E">: </span><span style="color:#032F62">获取方式同上</span></span><span class="line"><span style="color:#22863A">  avatar</span><span style="color:#24292E">: </span><span style="color:#032F62">"retro"</span><span style="color:#6A737D"> # 可选：https://valine.js.org/configuration/#avatar</span></span><span class="line"><span style="color:#22863A">  placeholder</span><span style="color:#24292E">: </span><span style="color:#032F62">评论框的 placeholder</span></span><span class="line"><span style="color:#22863A">  notify</span><span style="color:#24292E">: </span><span style="color:#005CC5">true</span></span><span class="line"><span style="color:#22863A">  verify</span><span style="color:#24292E">: </span><span style="color:#005CC5">false</span></span></code></pre><ul><li><a href="https://gist.github.com/fengkx/b7fe7b576b3f2822e872930a552e8152">放在 theme/next/layout/_third-party/comments</a></li><li><a href="https://gist.github.com/fengkx/3e3d1fa7ead31d92228716f9e6ea7c91">放在 themes/next/layout/_partials/ </a></li><li><a href="https://gist.github.com/fengkx/66dbdfca6579d77dc6a46eadc11c431b">放在 themes/next/layout/_macro </a></li></ul><p>EOF</p>]]></content><summary type="html">&lt;p&gt;不像动态博客一般带有评论系统，静态博客的评论只能借助第三方。刚开始搭这个博客的时候，传言中的多说已经死了，用的是友言。当时看它的&lt;a href=&quot;http://uyan.cc&quot;&gt;网站&lt;/a&gt;还有评价，感觉快要凉了。&lt;a href=&quot;https://archive.fo/Yl</summary><category term="Hexo" scheme="https://www.fengkx.top/tags/Hexo/"/><category term="评论系统" scheme="https://www.fengkx.top/tags/%E8%AF%84%E8%AE%BA%E7%B3%BB%E7%BB%9F/"/></entry></feed>