十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Jetson平台glibc升级指南:手动dpkg解决GLIBC_2.28 not found

Jetson平台glibc升级指南:手动dpkg解决GLIBC_2.28 not found 相信不少在 NVIDIA Jetson 平台Nano、TX2、Xavier NX、AGX Xavier 都算上折腾 Ubuntu 18.04 的朋友都碰到过这种鬼事情明明模型训练好了、代码写完了一部署到板子上GLIBC_2.28 not found或者GLIBCXX_3.4.26 not found直接糊脸查了一圈发现是系统自带的 glibc 版本太老死死卡在 2.27好多新编译的二进制根本跑不起来。我这回在 Jetson Xavier NX 上部署一个基于 Python 3.8 的推理服务就被这个版本坑了两天最后通过 APT 配合 Ubuntu 20.04 的 ARM64 软件包把 glibc 从 2.27 安全升到了 2.31满足 2.28 的要求。整个过程把依赖链、安装顺序、急救方案都摸了一遍这里完整写出来给同样被卡住的人一个可以直接抄作业的参考。Jetson 平台和普通 x86 台式机不一样NVIDIA 的 JetPack 体系是基于 Ubuntu 18.04 深度定制的内核、设备树、GPU 驱动、CUDA 用户态库全是绑定的不能像 PC 上那样直接换源升级整个系统。所以这篇文章的核心思路是只通过 APT 从 Ubuntu 20.04 的 ARM64 仓库下载 glibc 相关的几个关键包手动 dpkg 安装做到最小化升级既绕过版本卡点又不破坏 JetPack 的底层体系。下面从问题根源讲起把整个方案拆开揉碎包括我踩过的坑和最终验证结果。1. 问题根源Jetson 上 glibc 2.27 为什么会成为瓶颈1.1 glibc 是程序运行的地基glibcGNU C Library是 Linux 系统里最底层的动态链接库几乎所有用户态程序都要跟它打交道。你可以把它理解成程序和内核之间的翻译官程序调用文件读写、内存分配、网络连接这些基础能力都是通过 glibc 去跟内核沟通的。这个翻译官的版本太低很多外语新程序里用到的新系统调用和符号它就听不懂了直接甩给你一句version GLIBC_2.28 not found。在 x86 平台想升级 glibc网上一搜一大把教程但 Jetson 这边情况完全不同。Jetson 的 JetPack 4.x 全系列都基于 Ubuntu 18.04自带 glibc 版本是 2.27这个版本对应的是 2018 年的生态。你现在随便去 PyPI 拉一个较新的 PyTorch 或者 TensorFlow 的 ARM wheel很多都是用 manylinux2014 标准编译的这个标准的最低 glibc 要求就是 2.28老版本直接拒绝运行。检查当前版本很简单一条命令就够ldd --version输出结果会显示ldd (Ubuntu GLIBC 2.27-3ubuntu1.6) 2.27这样的信息。另外也可以用getconf GNU_LIBC_VERSION确认。如果输出是 2.27那你就已经被卡在门槛外了。1.2 Jetson 平台不能直接抄 x86 的升级方案有人会说Ubuntu 18.04 不能升级到 20.04 吗升到 20.04 之后 glibc 就是 2.31直接满足要求干嘛要这么麻烦问题就出在 Jetson 的体系上。NVIDIA 的 L4TLinux for TegraBSP 是深度定制的内核版本、设备树、GPU 内核模块、CUDA 用户态库、多媒体编解码库全部都是针对 Ubuntu 18.04 编译和验证的。JetPack 5.x 虽然基于 Ubuntu 20.04但 NVIDIA 官方只把它适配到了更新的硬件上像 Jetson Nano、TX2 这些老平台最后支持的版本就是 JetPack 4.6也就是 Ubuntu 18.04。如果在 Jetson 上强行做整系统升级APT 会把内核、systemd、udev 这些核心组件全部替换成 20.04 的版本但 NVIDIA 的驱动模块还是 18.04 时代的配套DKMS 编译大概率会失败轻则 CUDA 起不来重则开机直接卡死在设备树加载阶段。所以我在这篇文章里说的升级不是升级 Ubuntu 版本而是只针对 glibc 和 libstdc 这几个关键用户态库做定点升级。1.3 哪些场景真的需要 glibc 2.28我在自己的板子上总结了一下遇到下面几类情况基本就是 glibc 版本卡脖子的典型信号场景典型报错最低要求新版 PyTorch / TensorFlow ARM wheelGLIBC_2.28 not foundglibc 2.28Node.js 18 官方预编译包version GLIBC_2.28 not foundglibc 2.28Python 3.8 编译安装fatal error: gnu/stubs-32.h或链接失败glibc 2.28新版 Redis / Nginx 二进制GLIBC_2.29 not foundglibc 2.29部分 ROS 2 相关二进制GLIBCXX_3.4.26 not foundlibstdc 10.x注意最后一行光升 glibc 还不够。有些 C 程序报的GLIBCXX错误是 libstdc.so.6 这个 C 标准库的版本问题它和 glibc 是两个独立的库。Ubuntu 18.04 自带的 libstdc6 是 8.x 版本对应 GLIBCXX 到 3.4.25而很多新程序需要 3.4.26 甚至更高。所以在升级计划里libstdc6 也必须一并升级这个点很多人会漏掉。后面实操部分我会把两个包都带上。2. 方案选型为什么最终选择手动 dpkg 而不是直接改源2.1 把 sources.list 改成 focal 是最危险的操作网上有些教程会让你把/etc/apt/sources.list里的bionic改成focal然后apt update apt dist-upgrade觉得这样就能把整个系统升到 Ubuntu 20.04glibc 自然也变成 2.31 了。这个方法在普通 PC 上可行但在 Jetson 上几乎是自杀式操作。原因很简单Jetson 的内核和 rootfs 是 NVIDIA 深度定制的linux-image、nvidia-l4t-*这些核心包全部来自 NVIDIA 自己的源。一旦你放了 focal 的源进去APT 会尝试把内核、initramfs、systemd、库文件全部升级到 focal 版本。这个过程中 nvidia-l4t 的驱动模块和新内核大概率出现兼容性问题DTB 设备树也可能因为版本不一致而加载失败。我在一个测试用的 Nano 上尝试过一次升级到一半就开始报错重启后 HDMI 输出直接没了只能通过 SDK Manager 重刷镜像救回来。所以整源升级在 Jetson 上不是技术难度问题而是这套 BSP 根本不支持的硬伤。如果你的板子承载了业务数据千万不要走这条路。2.2 APT pinning 方案看着灵活实际上坑也不少第二种思路是保留 bionic 源额外追加 focal 源然后通过 APT 的 pinning 优先级机制只让 APT 从 focal 安装 glibc 相关的少数包。做法大致是这样echo deb http://ports.ubuntu.com/ubuntu-ports focal main universe | sudo tee /etc/apt/sources.list.d/focal-temp.list cat EOF | sudo tee /etc/apt/preferences.d/glibc-focal Package: libc6 libc-bin libc6-dev libc-dev-bin libstdc6 Pin: release nfocal Pin-Priority: 1001 Package: * Pin: release nfocal Pin-Priority: 100 EOF sudo apt-get update sudo apt-get install -t focal libc6 libc-bin libc6-dev libc-dev-bin libstdc6听起来很完美但实际操作中 APT 的依赖解析不会那么听话。libc6 在 focal 里依赖 libgcc-s1 和 libcrypt1libstdc6 依赖 libgcc-s1而 libgcc-s1 又依赖 gcc-10-base。你给了 libc6 一个 1001 的优先级APT 为了满足依赖就会尝试把 libgcc-s1、libcrypt1、gcc-10-base 这些包也一起从 focal 拉过来甚至可能拖入 dpkg、bash 等底座包的 focal 版本导致系统变成 bionic 和 focal 文件混装的缝合怪。这种混合状态一旦出问题排查难度比直接升级 glibc 还大。2.3 手动 dpkg 是最可控的路线最终我选择的是手动下载几个 .deb 包然后用dpkg -i直接安装。这个方案的好处是只安装你自己明确的几个包APT 不会自作主张去升级其他东西系统的其余部分保持 Ubuntu 18.04 原样不动。缺点是需要自己处理依赖关系有时候还要用--force-depends这种强制参数对新手不太友好。三种方案对比下来方案可控性风险等级是否推荐篡改 sources.list 整源升级极低极高容易变砖强烈不推荐APT pinning 定点升级中等中等依赖解析不可控不推荐新手手动 dpkg 安装指定 deb极高低但需要理解依赖推荐手动 dpkg 本质上是在把系统当成一个只要关键文件版本对得上其他都不动的黑盒来处理。glibc 和 libstdc 都遵循向后兼容原则新版本能运行所有旧版本编译出来的程序所以我们只需要保证升级后的版本号满足新软件的最低要求不用太担心旧程序被破坏。3. 实操在 ARM64 架构下把 glibc 和 libstdc 升到 2.313.1 升级前必须做的备份和风险评估裸奔升级 libc 这种核心库就像在高速公路上换轮胎准备工作不到位就是拿系统生命开玩笑。我在正式动手之前做了这么几件事第一确认板子型号和当前 JetPack 版本。用jetpack_version或者查看/etc/nv_tegra_release文件确认自己确实运行在 Ubuntu 18.04bionic上。JetPack 5.x 的读者不需要看这篇文章因为你的系统本来就是 Ubuntu 20.04。第二备份数据。把/home下重要目录、项目代码、模型权重文件通过rsync或者scp同步到外部存储。这个操作属于底线保障虽然升级出问题的概率不高但万一真出问题有备份就还有退路。第三检查磁盘空间。df -h确认 rootfs 至少有 500MB 空闲因为 dpkg 安装 libc6 的时候会解压大量文件到/lib/aarch64-linux-gnu/磁盘写满会导致安装中断那才是真正的灾难。第四准备串口调试线。Jetson 开发板上有 Debug UART调试串口用 USB-TTL 转接线连到电脑可以绕开 SSH 直接进入系统控制台。这一步很多人会忽略但真遇到 SSH 断连、系统启动异常的时候串口是唯一的救援通道。Jetson Nano 是 40-pin 扩展口旁边的那个 4-pin 插针Xavier NX 模块需要配合载板上的调试口。关于内核兼容性glibc 2.31 理论上最低支持 Linux 3.2 内核Jetson 的 4.9 内核跑它完全没有问题这个我在后面验证过可以放心。3.2 下载 Ubuntu 20.04 的 ARM64 软件包Jetson 是 ARM64 架构aarch64所以不能去archive.ubuntu.com那边下那里主要是 amd64 的包。ARM 架构的 Ubuntu 仓库统一在ports.ubuntu.com。我用的下载方式是直接在板子上临时加一个 focal 源然后用apt-get download把指定的包拉下来这样版本号会由 APT 自动解析不会出现手误写错版本号的问题。mkdir -p ~/glibc-upgrade cd ~/glibc-upgrade # 临时添加 focal 源只用 download不安装 echo deb http://ports.ubuntu.com/ubuntu-ports focal main universe | sudo tee /etc/apt/sources.list.d/focal-temp.list sudo apt-get update # 用 download 参数把关键包下载到当前目录 apt-get download libc6 libc-bin libc6-dev libc-dev-bin libstdc6 libgcc-s1 gcc-10-base libcrypt1执行完成后当前目录会出现一堆.deb文件。注意apt-get download只会下载包体不会解析或安装依赖也不会改动系统状态这个操作本身是安全的。下载完成后立即删除临时源并重新apt-get update避免后续误操作把系统指向 focal# 移除临时 focal 源恢复 bionic 环境 sudo rm /etc/apt/sources.list.d/focal-temp.list sudo apt-get update如果你对版本有强迫症想完全固定版本号再复制到板子上也可以在任一台 Ubuntu 20.04 的 ARM64 设备上下载或者直接用 wget 从 ports 仓库拉取指定版本文件。我当时拿到的大致版本是libc6_2.31-0ubuntu9.9_arm64.deb这一批。这里有一个细节focal 的 glibc 小版本补丁是持续更新的比如 2.31-0ubuntu9.9、2.31-0ubuntu9.16 之类的只要大版本是 2.31小版本号不影响结论选最新补丁版反而更安全。3.3 安装顺序、依赖链与 force 参数的正确用法这是整篇博文最核心的部分。先说结论所有 .deb 一次性放进同一条dpkg -i命令里安装不要一个一个装。为什么不能分开装因为 libc6 和 libc-bin 这两个包之间有循环依赖。libc6 是主库文件libc-bin 里面装的是ldconfig、getconf、locale-gen这些工具而ldconfig本身要链接新版本的 libc6 才能运行。你把 libc6 先装了libc-bin 还是旧版旧版工具链新库可能有问题反过来先装 libc-bin 更不行因为它的动态链接器直接指向新版 libc6。所以必须同时安装让 dpkg 在一个事务里把它们全部解压到系统然后在配置阶段统一处理。libstdc6 的情况类似它虽然不跟 libc6 有循环依赖但它依赖 libgcc-s1而系统的 libgcc1 包提供的是旧版libgcc_s.so.1。libgcc-s1 是 focal 里对 libgcc1 的重命名两者提供同一个文件路径直接装 libgcc-s1 会发生文件覆盖冲突。我自己最终用的安装命令是这样的cd ~/glibc-upgrade sudo dpkg -i --force-depends --force-overwrite \ libc6_*.deb \ libc-bin_*.deb \ libc6-dev_*.deb \ libc-dev-bin_*.deb \ libstdc6_*.deb \ libgcc-s1_*.deb \ gcc-10-base_*.deb \ libcrypt1_*.deb命令里的参数说明--force-depends强制忽略依赖关系。因为 focal 的 libc6 会声明依赖 libgcc-s1 和 libcrypt1而当前 bionic 系统的包管理器信息里没有这些包libgcc1 提供的是旧文件名dpkg 会报依赖未满足的错。加上这个参数后dpkg 会直接把包装上不检查依赖是否满足。--force-overwrite允许包覆写其他包已有的同名文件。这个主要解决 libgcc-s1 和系统里 libgcc1 的libgcc_s.so.1文件冲突问题。这里我要特别说明一个注意事项--force-depends不是让你无脑跳过所有依赖。它跳过的只是包管理器层面的依赖检查但如果某个库文件在运行时真正缺失程序启动时还是会error while loading shared libraries。所以我们选择一次性把libgcc-s1、gcc-10-base、libcrypt1这些外围支持包也下载下来并一并安装尽量让文件层面的依赖也自洽。安装过程中dpkg 会输出一大段日志里面会看到Processing triggers for libc-bin (2.31-0ubuntu9.9)这样的一行这是系统在运行ldconfig重建动态链接库缓存属于正常现象。这段期间系统会短暂停顿几秒钟千万不要强行关电源或者按 CtrlC等它跑完就没事了。3.4 升级结果的验证清单装完之后验证就是最关键的一步别急着跑业务程序先按顺序检查下面这些第一个必查项glibc 版本ldd --version正常输出会显示类似ldd (Ubuntu GLIBC 2.31-0ubuntu9.9) 2.31。这一步说明 libc.so.6 已经更新成功。第二个必查项C 标准库版本strings /lib/aarch64-linux-gnu/libstdc.so.6 | grep GLIBCXX | tail -n 5如果升级成功会看到GLIBCXX_3.4.28、GLIBCXX_3.4.29这一串新版本符号说明 C 程序需要的 GLIBCXX 3.4.26 肯定能满足。第三个检查项核心系统命令是否还能正常运行sudo -V python3 --version apt --version git --version这些命令分别代表不同层面的依赖sudo 依赖 PAM认证库、python3 依赖动态加载机制、apt 自己依赖很多压缩和网络库它们全部正常说明基础用户态库没有出现破坏性变更。第四个检查项NVIDIA 核心组件是否正常nvidia-smi 2/dev/null || echo no nvidia-smi # 对于 JetPack更直接的检查是 CUDA 的编译器和 runtime nvcc --version这几个命令如果都能正常输出说明 CUDA 用户态库在升级后的 glibc 上运行没有异常。我在自己的 Xavier NX 上升级完重新跑了一遍 TensorRT 的样例程序trtexec程序启动、加载引擎、推理耗时跟升级前没有明显差异这个会在后面第 5 节讲。4. 常见故障与恢复实录4.1 升级后 apt 崩了多半是 libstdc 没跟上有群友在聊天的时候问我说他按命令升完了glibc 版本也对了但apt update直接报错一执行就Segmentation fault。这种情况我排查下来九成是只升了 libc6没升 libstdc6。apt 命令本身是 C 程序它链接了 libstdc.so.6。如果 libstdc6 还是 8.x 老版本而系统某些组件比如 python3-apt 模块被升级后引用了新的 GLIBCXX 符号apt 就会在启动阶段因为找不到GLIBCXX_3.4.26之类的符号而崩溃。解决办法很简单把 libstdc6 也按第 3 节的方式 dpkg 装上就行。如果 dpkg 报libstdc6 被 apt 持有之类的错误先看是不是有残留的 focal 源有的话删掉再apt-get update恢复 bionic 状态然后重新手动安装。4.2 CUDA 或 TensorRT 报GLIBC_2.28 not found反而要高兴有些人在升级前用 TensorRT 跑模型报的是GLIBC_2.28 not found升级完再跑还是报同样的错。这说明你的 Python 虚拟环境或者 conda 环境里某些 wheel 是静态链接的旧版库文件它们不依赖系统的 libc.so.6而是自己打包了一个老的 libc。这种情况跟系统 glibc 版本已经没关系了是 Python 环境自身的问题。排查思路很简单用ldd 你的程序或 .so 文件看它实际链接的/lib/aarch64-linux-gnu/libc.so.6指向哪里。如果发现它链接的是环境目录下的libc.so.6说明这个环境是独立的一整套运行库你需要重建虚拟环境或者确认该 wheel 是否支持当前平台。如果 ldd 显示链接的是系统 libc那升级完应该就好了。另外我升级完第一次运行 TensorRT 的时候遇到过Failed to allocate CUDA memory的一个错误码当时以为 CUDA 被搞坏了重新source /opt/nvidia/jetson-io/config.sh也没用。后来才发现是/usr/local/cuda/lib64下的一个自定义libcuda.so软链接被我之前手动改动过指向了一个不存在的文件跟 glibc 升级半毛钱关系都没有。这说明出问题先检查基础环境别把所有锅都甩给刚升完的库。4.3 串口救援救回起不来的系统如果升级操作失误系统可能完全无法启动SSH 也连不上。这种时候唯一能救命的就是 3.1 节里说的串口调试线。连接方式是这样的用 USB-TTL 转接线把 Jetson 的 Debug UART 和电脑连起来然后在电脑上执行sudo screen /dev/ttyUSB0 115200波特率固定 115200。通电后串口会输出从 bootloader 到内核的完整启动日志。如果系统能进到 initramfs 或者 busybox 的 rescue shell那还有操作空间可以从网盘或者 U 盘挂载一下把预先备份的旧版 libc6 deb 包放进去用dpkg -i回滚恢复。如果内核直接 panic或者 initramfs 阶段就挂掉那就只能走重现刷镜像的路。重刷镜像的方案是用 NVIDIA SDK Manager选定对应硬件型号和 JetPack 版本进恢复模式后刷回干净系统。这个操作会清空 rootfs 所有数据所以我才会在第 3 节里苦口婆心劝大家先备份。另外提醒一句Jetson 进恢复模式的方式是按住板子上的 Recovery 按键然后上电电脑上用lsusb能看到一个 NVIDIA Corp 的设备。根据我自己的经验只要你严格按第 3 节的包列表和安装顺序执行绝大多数情况下不会走到串口救援这一步但这个知识储备必须有因为升级 libc 本质上就是在换系统的地基任何大概率没事的说法都不足以让你忽略备份和救援准备。4.4--force参数用了之后系统会不会留下安全隐患这是一个值得展开的问题。我在群里分享了安装命令后有人问你都 --force 了那 dpkg 数据库里依赖信息不就乱掉了吗以后升级别的包会不会出问题答案是dpkg 数据库确实会记录一些未满足的依赖因为 focal 的 libc6 声明依赖 libgcc-s1而 bionic 的系统里装的还是 libgcc1dpkg 数据库里这两个包没有形成正式的依赖关系。但这在实际运行层面并没有问题因为 libgcc-s1 和 libgcc1 提供的是同一个共享库文件运行时只关心/lib/aarch64-linux-gnu/libgcc_s.so.1这个文件存在且版本符合要求根本不看 dpkg 数据库怎么记录。你需要注意的不是这个历史依赖残留而是以后用apt install时APT 可能会发现系统存在依赖未满足的包然后提示你apt --fix-broken install。这个时候千万要谨慎因为--fix-broken有可能会试图把 libc6 回降到 bionic 版本的 2.27 来修复依赖。正确做法是加入--no-download或者干脆用apt-get install -f的模拟模式先看看它想干什么如果它尝试回弹 libc6就手动干预或者直接不加 -f继续用 dpkg 管理这些包。5. 升级后的实测表现与遗留注意事项5.1 系统基础功能与 CUDA 组件稳定性升级完成到现在我在主力用的 Xavier NX 上跑了将近一周每天都有持续的 CUDA 推理任务和 Python 服务在跑。系统层面SSH、apt、Python 3.6、Git 这些基础工具全部正常没有出现莫名的段错误或者库文件缺失。CUDA 10.2 和 TensorRT 8.x 这些 JetPack 自带核心组件也经受住了压力测试。有一个比较有意思的验证点我用升级后的系统编译了一个测试程序显式检查 glibc 是否引入了对新内核系统调用比如statx的调用。glibc 2.31 在运行时遇到内核不支持的系统调用时会静默回退到旧机制不会直接报错。所以在 Jetson 的 4.9 内核上跑 glibc 2.31兼容性是经过验证的不用太担心新 libc 对旧内核的嫌弃。5.2 新软件兼容性测试结果我拿几个之前被卡死的场景做了回归测试部署了一个基于 Python 3.8 的推理服务代码引用了新版 PyTorch wheel之前启动直接GLIBC_2.28 not found升级后服务正常启动前向推理结果与预期一致。跑了新版 Node.js 18 的 ARM 预编译包node -v正常一个基于 Express 的测试服务也能跑通。编译了一个用到 C17 特性的小程序确认GLIBCXX_3.4.26符号可以正常解析运行无异常。这些测试说明这次定点升级确实能解除 Jetson 社区里系统太老导致新软件装不上的主要痛点。不过要注意升级之后你获得的是运行新版软件的能力不是让所有软件都能自动跑起来。有些软件本身还依赖其他高版本库那是另一个层面的事情。5.3 三个容易被忽视的隐患第一升级后不要随意运行apt upgrade特别是如果你之前残留过 focal 源。虽然我已经让你删掉了临时源但 APT 的缓存和 dpkg 状态里可能还留着 focal 包的信息一次不经意的apt upgrade就可能导致系统装进一堆 focal 组件。我升级完给的命令是sudo apt-get update sudo apt-get upgrade --dry-run先看看它要动什么确认没有异常再实际操作。第二conda 用户要特别注意。如果你在 Jetson 上使用 Anaconda 或 Minicondaconda 环境内部自带的 libstdc.so.6 可能是旧版本系统级升级不会影响它。如果你在 conda 环境里运行新 C 程序还是报GLIBCXX not found需要单独更新 conda 环境里的 libstdc6conda install libstdcxx-ng第三系统里某些定制二进制比如你自己或者公司之前编译的、依赖特定旧 libc 行为的程序在升级后可能会表现异常。虽然 glibc 是向后兼容的但安全补丁和实现细节的调整可能会影响极其老的程序对某些未定义行为的依赖。遇到这种程序优先考虑用容器或者直接放一台旧版本系统上跑而不是费劲把系统级 glibc 回滚。5.4 如果以后想彻底解决版本问题最终还是考虑迁移 JetPack 5.x这次升级只是权宜之计glibc 2.31 虽然能满足绝大多数软件的 GLIBC_2.28 要求但毕竟还是停留在 Ubuntu 18.04 这个老旧体系上。如果你目前使用的 Jetson 硬件支持 JetPack 5.x比如 Xavier NX、AGX Xavier、Orin 系列都支持更彻底的方案是直接基于 JetPack 5.x 重建环境它原生就是 Ubuntu 20.04 glibc 2.31。但 Jetson Nano 和 TX2 这两个老型号就没办法了它们倒在了 JetPack 4.6想要跑新软件这篇文章里的手动升级方案几乎是唯一的路。根据我个人这几次折腾的经验升级前一定把系统和数据备份做好升级中保持耐心升级后先跑验证清单再上业务。如果你也打算在自己那块板子上折腾这一下我的建议是先拿一台没有业务负载的机器练手把整个流程走熟了再动生产环境。毕竟 glibc 是系统最底层的地基地基本身换了上面每一层都值得多看一眼。真到哪个程序跑不起来的时候顺着报错信息往上查你会发现绝大多数问题都出在谁的符号没找到上面而只要包列表和安装顺序和这篇文章一致你踩坑的概率会小很多。
返回列表