新闻详情

新闻详情

首页 / 资讯中心 / 详情

ATF固件架构与BL31启动流程深度解析:从安全启动到平台移植

发布时间:2026/9/9 10:07:22来源:尧图网络
ATF固件架构与BL31启动流程深度解析:从安全启动到平台移植
1. ATF到底在解决什么问题从一次启动失败说起先讲个我实际调试的经历。有次我在一块自定义ARMv8开发板上做启动适配U-Boot死活起不来串口完全没有输出用JTAG挂上去看PC指针发现卡在EL3的某个异常向量里出不来。折腾了一整天最后定位到是BL31的entrypoint_info里传给U-Boot的bl33_ep_info-spsr设置错了导致U-Boot跳转时中断被屏蔽CPU直接卡死在等待中断的循环里。这件事让我对Arm-Trusted-Firmware简称ATF现在很多地方也叫TF-A有了很深的敬畏。你平时可能只是把ATF当成一个“启动链路上必须烧录的二进制”觉得它无非就是把U-Boot叫起来而已。但一旦你开始做真正的平台移植、做安全启动、做固件审计你会发现ATF的复杂度远超预期——它是一个横跨EL3、S-EL1、S-EL0多个特权级的完整软件栈里面同时塞着安全监控、PSCI电源管理、中断路由、可信启动、运行时服务等一大堆互相关联的子系统。这篇文章我想从一个做底层BSP移植和固件安全审计的人的角度把ATF的架构、源码链路、安全设计、以及平台移植落地的完整流程拆开讲一遍。不会只停留在“怎么编译”的层面而是会深入到你为什么要写那些平台宏、为什么需要实现某个回调、BL31在跳转前后到底干了什么。我个人觉得如果你是做固件开发、BSP适配、安全启动集成的小伙伴这篇文章能帮你省下至少一周的摸索时间。即使你暂时不做底层了解ATF的架构方式对理解ARM体系的分层启动机制也很有帮助。注意本文基于TF-A 2.9的代码结构来分析部分函数名在更早版本可能不同但整体架构一致。2. 先从架构全景下手BL1、BL2、BL31到底谁管谁2.1 启动链路全景BL0到BL33一趟走完ARMv8体系下CPU上电后先跑固化在ROM里的BootROMBL0它负责加载下一级镜像。很多人会搞混一件事BL0并不是ATF的一部分它是SoC厂商标定死的ROM代码你没法改。真正属于ATF的是BL1、BL2、BL31这三个组件再加上BL32比如OP-TEE是可选的TEE操作系统。我习惯用一个比方来理解这条链路BL1是Rom里的“种子”权力极小但绝对可信BL2是“搬运工”负责把后续镜像从Flash搬到内存里BL31是“管家”驻留在EL3负责运行时事务BL33就是最终要运行的Normal World系统通常是U-Boot或内核。具体分工如下组件运行位置特权级职责生命周期BL1BootROMSRAMEL3加载BL2、建立初始EL3环境启动后基本退出BL2SRAM/DRAMS-EL1加载BL31/BL32/BL33、校验镜像签名跳转BL31后退出BL31DRAMEL3运行时常驻处理SMC、PSCI、中断路由一直存活BL32DRAMS-EL1可选TEE OS提供安全服务一直存活可动态加载BL33DRAMEL2/EL1Normal WorldU-Boot/内核接管整个系统这里有一个关键点值得单独拎出来说BL1是整个链条里唯一的“天然信任根”。它必须存储在不可篡改的ROM里所以ATF的签名和校验机制设计是围绕BL1展开的。你在做安全启动时ROTPKRoot of Trust Public Key就是烧在BL1里的公钥信息之后所有镜像的签名验证都是基于它的。2.2 BL31运行时框架为何它值得你花最多时间读大多数做移植的人第一次上手读ATF源码都会直接瞄BL31因为BL31是运行时间最长、涉及逻辑最复杂、出错也最难定位的部分。BL31的入口是bl31_main()它的执行流程可以精简成四大阶段初始化el3_arch_init设置异常级别环境bl31_platform_setup做平台级初始化比如配置GIC、PSCI相关的寄存器。注册服务bl31_lib_init初始化EL3 runtime库然后bl31_register_bl32_init和bl31_register_bl33_init将BL32/BL33的入口信息存入全局结构体。进入主循环bl31_main()最后调用bl31_prepare_next_image_entry()设置好下一阶段的执行上下文然后通过el3_exit降级跳到BL33。运行时兜底BL31跳出去之后并不是“死掉”了它仍然驻留在EL3等待来自Normal World或Secure World的SMC请求随时可以接管服务。我最开始读这套代码的时候有个误区以为BL31把控制权交给BL33后就算完成任务剩下的就是U-Boot的事了。但错了——你在U-Boot里执行psci相关的操作比如CPU hotplug、系统关机本质上是触发了一个SMC异常CPU会重新切到EL3去执行BL31注册好的PSCI处理函数。所以BL31是整个系统生命周期内的“隐形管家”这才是它叫runtime firmware的原因。2.3 镜像格式与加载过程的工程细节镜像文件方面BL1、BL2、BL31最终都会被打包成FIPFirmware Image Package。你去跑make之后会在build/platform/release/目录下看到一大堆文件常用的有bl1.binBL1镜像一般烧在SRAM或ROM里。bl2.binBL2镜像需要可以放到Flash的固定偏移。bl31.binBL31镜像通常由BL2加载到DRAM地址。fip.bin打包了上述所有镜像的单一文件U-Boot或烧录工具可以直接刷。我建议你自己动手解析一下FIP的结构格式非常简单头部是FIP_TOCTable of Contents按UUID区分每个镜像条目后面依次跟着镜像数据。你只要打开fip.bin看一眼就能理解U-Boot的fip_update命令为什么要拿一个--bl31参数——它就是在FIP_TOC里找到匹配UUID的bl31.bin条目然后替换内容。实操小技巧如果你只改了BL31而没重新打包FIP烧录后系统多半是启动不了的因为BL2按FIP_TOC里的镜像尺寸和偏移去加载。很多新手踩这个坑建议每次make之后顺手看一下fip.bin的时间戳是否更新。3. 安全固件工程审计证书链、TBBR与异常路径3.1 从“能跑”到“安全”之间格着多少层校验ATF里安全启动的最高形态是TBBRTrusted Board Boot Requirements。开启方式是在编译时加一行TRUSTED_BOARD_BOOT1但加完之后你的镜像就再也不能裸奔了——BL2必须验证BL31/BL32/BL33的签名和证书BL1也要验证BL2的证书整个链条必须完整无误。很多朋友在开发阶段不关心证书跑的就是“非TBBR”模式。但如果你想做真正的产品固件TBBR这一步是绕不开的。因为一旦芯片进入量产BootROMBL0可能已经强制开启TrustZone和签名校验你的BL1必须能配合完成证书链验证否则芯片连BL2都加载不了。TBBR的证书链大致如下ROTPK烧在BL1里的根公钥一般不直接参与签名而是通过哈希校验。BL2证书由ROTPK对应的私钥签发包含BL2镜像的哈希。BL31/BL32/BL33证书分别由BL2证书对应的私钥签发包含各个镜像的哈希和加载地址。在TF-A的源码里证书处理相关代码在drivers/auth/目录下。以mbedtls为底层实现TF-A会调用auth_mod_verify来对镜像做哈希比对和签名验证。3.2 审计资产清单这几处代码你必查如果你接了一个安全审计任务拿着第三方给的ATF固件去反编译或Code Review我建议你重点关注以下这些点1. BL2的镜像加载地址有没有被篡改的可能。platform_get_bl31_load_info()这些接口返回的加载地址如果能在Normal World被改写就等于给了攻击者一个任意地址写入口。2. BL31是否启用了WXNWrite-XOR-Never内存属性。在EL3的页表设置里如果代码段是可写的那就给攻击者灌入恶意代码留了后门。ARMv8.1之后用MT_CODE/MT_RO_DATA属性可以严格控制权限但很多定制平台往往为了跑起来方便而放宽了这个限制。3. 检查SMC调用处理逻辑有没有越界访问。BL31暴露给Normal World的SMC接口是攻击者最容易触碰的外层攻击面。你可以重点看smc_handler64里对参数的解析很多平台自己加的vendor-specific SMC handler经常忽略参数合法性校验。4. 检查GIC配置。中断路由错误会导致安全中断被发到Normal World这是极其危险的。在plat_ic_setup相关代码里必须确保安全中断SGI/PPI都配置给EL3或安全侧处理。3.3 签名验签的实际操作用工具链自己签发一套这里放一段我在项目里实打实跑过的签名命令供你参考。TF-A自带的cert_create和fip_create工具可以完成证书生成、签名、打包的全流程# 1. 生成Root私钥和公钥假设用RSA-4096 openssl genrsa -out rot_key.pem 4096 openssl rsa -in rot_key.pem -pubout -out rot_pub.pem # 2. 生成BL2证书并签名 cert_create -n trusted_boot \ --rot-key rot_key.pem \ --tb-fw-cert tb_fw.crt \ --tb-fw bl2.bin # 3. 生成BL31证书并签名 cert_create -n trusted_boot \ --rot-key rot_key.pem \ --nt-fw-cert nt_fw.crt \ --nt-fw bl31.bin \ --nt-fw-key bl31_key.pem # 4. 打包FIP fip_create --tb-fw bl2.bin --soc-fw bl31.bin \ --to-stdout fip.bin这里有个非常容易忽略的问题证书里不仅要包含镜像的哈希还必须包含加载地址。TF-A的BL2在验证完BL31的签名后会用证书里记录的加载地址来做memcpy如果地址对不上即使签名是有效的也会拒绝加载。所以你在手工生成证书时--soc-fw的加载地址参数必须和平台宏里定义的BL31_BASE完全一致。我这里演示的是简单流程实际产品一般会用到多级私钥体系比如还分厂商级密钥和产品级密钥但原理是同一套。3.4 固件审计中一个容易被忽视的坑MBEDTLS的配置TF-A默认使用mbedTLS做验签但这个库的配置直接影响你的安全强度和二进制体积。很多人直接从TF-A的默认配置复制一份就能用但如果你审计的固件里裁剪了某些算法比如去掉了SHA-256、只保留SHA-384那证书链必须相应调整。我在一个客户项目里就碰到过这种情况第三方固件为了压缩BL2体积把MBEDTLS_SHA256_C关掉了结果烧进去之后BL2验签失败设备无法启动。排查了整整一天最后追到mbedTLS配置头文件才发现问题。所以你在做审计的时候一定要连mbedTLS的mbedtls_config.h一起看光看TF-A本身的代码是不够的。4. 平台移植落地实操从零把一个新板子跑起来4.1 为什么不能直接拿别人的platform目录来用很多做板级支持的同学拿到一个新的SoC第一反应是“找个参考平台改一改”。这个思路是对的但也是最容易翻车的。ATF的平台代码不是单纯的“寄存器配置集合”它和SoC内部的启动地址映射、TrustZone地址空间、GIC连接方式、甚至console driver的硬件寄存器基址都强绑定。你直接改个宏名就跑十个里有九个起不来。我做平台移植的经验是选参考平台时优先挑相同内存映射风格的平台而不是挑SoC相似的平台。比如你的新芯片是全新的内存控制器地址布局但它的串口控制器和某一款QEMU平台一致那你宁可拿QEMU的platform做底子也不要硬套一个寄存器地址对不上的高大上平台。4.2 五个核心适配点以我基于FVPFixed Virtual Platform做二次移植的经验一个最小的ATF平台支持至少需要改以下几个文件1.plat/your_platform/platform.mk这是平台编译入口需要定义BL31_BASE、BL32_BASE、BL33_BASE等内存布局宏。还记得我开头说的那次调试吗BL33_BASE设错会导致U-Boot加载地址和BL31跳板地址不一致表现就是“U-Boot有代码但死活跑不过第一行”。2.plat/your_platform/plat_setup.c实现plat_get_next_bl_params()、bl31_platform_setup()等接口。这里是初始化串口、GIC、PSCI依赖硬件的地方。要注意串口初始化函数必须在early_platform_setup里调用否则你连报错信息都看不到。3.plat/your_platform/platform_def.h定义平台级宏比如PLAT_PHY_ADDR_SPACE_SIZE物理地址空间大小和PLAT_VIRT_ADDR_SPACE_SIZE虚拟地址空间大小。这两个值决定EL3页表覆盖范围设小了会导致后续访问异常。4.plat/your_platform/bl31_plat_setup.cBL31平台逻辑负责把GIC配置好、把runtime_svc注册到SMC表中。我这里踩过一个坑忘记注册PSCI服务导致U-Boot里执行smc #0后直接掉到unknown_svc_handler里。5.plat/your_platform/console平台串口驱动必须实现console_init和console_putc。如果你的板子没有现成驱动最小实现就是直接操作UART寄存器输出字符够调试就行。4.3 我推荐的移植顺序从“点亮串口”开始如果你拿到一块全新的板子我强烈建议按这个顺序来做每一步都能独立验证大大降低调试难度第一步编译一个最简BL31串口不初始化都行。先用make PLATplatform DEBUG1跑通编译环境把BL31裸编出来配合JTAG或硬件仿真器看能不能停在bl31_main里。第二步点亮串口。实现console驱动至少要把console_init和console_putc做出来。你可以在bl31_early_platform_setup里打印一行固定的字符串然后看串口工具有没有输出。这一步通了后面的所有调试才谈得上。第三步实现BL33跳转。先不配置GIC也不做PSCI只把BL33比如一个最小的U-Boot加载到对应地址然后用el3_exit跳过去。如果U-Boot能打印出第一行日志说明你的内存映射和跳转上下文基本没问题。第四步补GIC和PSCI。把中断控制器初始化、PSCI CPU开关接口补齐。到这一步你的平台已经可以正常启动Linux并进入系统剩下的都是细节优化。这个顺序最核心的逻辑是每一层都能在前一层验证的基础上继续而不是一次性写一大坨代码然后连编译都过不了。我的经验里80%的移植失败都发生在“一次性写太多”的情况。4.4 一个特别的坑从BL2到BL31的内存所有权交接BL2运行在SRAM里但BL31通常要加载到DRAM。这里面有个隐蔽问题BL2用到的堆栈、页表可能还在SRAM里而BL31使用的BL31_BASE地址如果在DRAM区域内你必须保证BL2在跳转前把该清理的缓存和TLB条目都给清理掉。具体来说BL2跳转过程中会通过bl2_el3_exit进入BL31这时mmu是被关掉的但dcache不一定关闭。如果你的BL31入口代码里有一处读不到最新数据的操作多半就是cache coherence问题。所以我建议在bl31_early_platform_setup的第一步就显式执行inv_dcache_all和tlbiall这能帮你排除一类极其难查的诡异问题。4.5 交叉编译环境的一次性配置指南ATF的编译离不开ARM交叉编译器。我在x86宿主机上常用的工具链是aarch64-none-elf-裸机版和aarch64-linux-gnu-Linux用户态版两者差异在于后的带glibc依赖。做ATF时裸机版工具链更合适因为它不依赖用户在操作系统上跑额外的动态库。配置命令大致如下export CROSS_COMPILEaarch64-none-elf- make PLATyour_platform DEBUG1 \ BL33../u-boot/u-boot.bin \ all fip如果报错找不到aarch64-none-elf-gcc多半是工具链没加进PATH。这里有个容易混的点有些人下载的是“arm compiler 5.06”这类32位ARM编译链那是给Cortex-M/A/R这些32位核用的不是AArch64。你如果误用32位工具链去编ATF编译根本过不去-marcharmv8-a会直接报不识别。5. 常见问题与排查技巧实录5.1 启动到一半就挂掉先学会用智械断点ATF的报错信息有些写在串口日志里但也有一类错误是在早期初始化阶段就直接挂掉连日志都打不出来。这时候最有效的手段就是JTAG/SWD加硬件断点在bl31_main入口打一个断点看能不能停住。如果停不住再看BL31_BASE地址处的二进制是否正确加载到DRAM里。我试过最离谱的一次BL31镜像加载地址完全正确但在bl31_main里的第一行zero_normal world context时直接触发同步异常——最后查出来是页表基址寄存器TCR_EL3的配置和物理内存规格不匹配导致DC ZVA指令访问到了一个不存在的物理地址。5.2 PSCI掉电/重启失效大部分是GIC的锅当你发现Linux里执行reboot或poweroff没反应甚至直接卡死十有八九不是PSCI逻辑本身的问题而是GIC里的中断路由没配置好。ATF在处理PSCI时会通过GIC来管理CPU的电源状态如果GIC的GICD_CTLR没有正确使能或安全中断映射错误PSCI的CPU_SUSPEND/CPU_ON命令就会卡在等待中断应答的循环里。排查方法先关掉GIC用一个极简的系统复位寄存器很多SoC有SYS_CFG类寄存器直接触发复位看能不能复位成功。能的话说明CPU电源域本身没问题焦点就回到GIC配置上。5.3 BL31打印一堆Error但系统还能跑不要被这种“假象”骗了。ATF里很多ERROR级别的打印并不会直接进入死循环而是返回一个错误码给上层。比如U-Boot请求了某个未实现的PSCI命令BL31会打印PSCI: Unsupported command然后继续运行。这种错误虽然不致命但说明你的平台代码里仍然有功能缺失比如CPU idle的PSCI_STAT查询没实现。5.4 一个排查速查表现象优先排查项参考位置串口完全无输出console驱动、UART基地址、bl31_early_platform_setup是否被调用plat_setup.cBL2加载BL31时报尺寸错误FIP里的BL31实际大小和BL31_BASE预留空间是否匹配platform_def.hU-Boot无法启动BL33_EP_INFO中的spsr和入口地址是否一致bl31_plat_setup.cSMC调用卡死是否注册了PSCI runtime servicebl31_plat_setup.cCPU hotplug失效GIC对PPI/SGI的中断路由配置plat_gic.c安全中断泄漏到Normal WorldGIC安全中断使能位与EL3 interrupt handlerplat_ic_setup5.5 我自己的两个独家排查习惯习惯一上线前先把所有DEBUG级别日志打开跑一遍。发布Release固件时很多人会关掉DEBUG日志但你在开发阶段一定要把LOG_LEVEL50VERBOSE打开看完整的BL1-BL2-BL31-BL33链路日志确认每个阶段的跳转地址都符合预期。我很多看似玄学的问题最后都在verbose日志里找到了蛛丝马迹。习惯二用QEMU/FVP先做一轮虚拟平台的“翻译”。很多ATF的通用逻辑错误用FVP一跑就能暴露出来因为FVP的CPU模型和中断模型非常标准。先在模拟器上把启动链路调通再上物理板子能让80%的移植问题在硬件调试之前就被消灭。6. 移植的另一个高价值入口与U-Boot的协作细节很多人在ATF移植成功后就以为万事大吉结果在U-Boot里又被卡了一回。这里必须强调一个点ATF和U-Boot之间不是简单的“谁先跑谁后跑”的关系它们之间有一整套接口协议要磨合。最典型的两个接口U-Boot作为BL33ATF通过bl33_ep_info结构体把入口地址、spsr、x0-x7参数传递给U-Boot。U-Boot的二阶段入口_main会从x0拿到dtb地址、x1拿到bootargs指针这些都是在ATF平台代码里预设的。如果你期望U-Boot从某个固定地址读设备树却没在bl33_ep_info里传过去那U-Boot只能用自己的默认值设备树可能读错地方。PSCI与U-Boot的CPU管理U-Boot里的psci驱动会通过SMC指令和BL31通信。如果你在ATF里只实现了CPU_ON而没有实现CPU_OFF那U-Boot执行cpu release或cpu disable会直接拿到错误码。我建议把PSCI_CPU_OFF、PSCI_CPU_ON、PSCI_SYSTEM_OFF、PSCI_SYSTEM_RESET这四个基础命令全部实现到位再考虑扩展命令。我自己在做平台适配的时候常用一个笨方法先在U-Boot命令行下手动执行smp相关的psci命令观察BL31侧打印看它是否进入了我预期的handler。这个方法虽然土但能快速定位问题是出在ATF的PSCI service还是U-Boot的PSCI driver。7. 安全固件后续演进的个人观察TF-A这个项目这两年变化很快除了常规的bug修复和安全补丁有几个方向值得关注一是FF-AArm Firmware Framework for Armv8-A的逐步落地。早期的PSCI和SMC接口风格比较“散装”各家平台各自定义。FF-A试图提供一个标准化的通信框架让EL3和其它安全组件之间的交互更统一、更安全。如果你在做新平台选型建议预留FF-A相关的接口位置别把自己绑死在老式SMC风格里。二是RSSRuntime Security Subsystem概念的兴起。传统的ATF把安全能力都放在EL3里而新一代芯片倾向于在SoC里独立出一个安全子系统来处理密钥、加解密等操作。这并不意味着ATF会消失而是它的职责边界会进一步收缩更纯粹地作为“启动可信根”和“安全通信通道”。三是对开源生态的依赖加深。现在很多SoC厂商都把自己的ATF和U-Boot还有内核放在同一个repo里发布版本之间的联动性更强。做平台移植时最好连U-Boot的版本一起锁定不要只锁定ATF的版本。否则ATF传到U-Boot的参数格式不兼容会浪费大量时间在莫名其妙的启动失败上。我个人在实际操作中的体会是ATF的学习和移植是一场耐心战但收益极大。你一旦把这套EL3的安全框架摸透了再回头看那些“怎么配置TrustZone”、“怎么保护安全内存”、“怎么实现安全启动”之类的问题都会觉得非常清晰。如果你正准备开始一个基于ARMv8平台的固件项目我的建议是先别急着上板把FVP上已经验证过的平台架构彻底跑通一遍再带着问题去碰你的物理硬件。这样踩坑最少效率也最高。
网站建设高端定制企业官网
RELATED

相关资讯

更多精彩内容,欢迎继续阅读

较早相关资讯

最新相关资讯

双通道模拟示波器:连续信号观测与相位关系分析核心指南 2026/9/9 10:37:33

双通道模拟示波器:连续信号观测与相位关系分析核心指南

1. 为什么今天还要学双通道模拟示波器?——被数字示波器“惯坏”后的真实代价 你有没有试过用一台崭新的数字示波器测一个老式功放的偏置电压,结果屏幕一闪,触发失败,再闪,还是失败,最后干脆显示“信号超出…

阅读更多 →
基于FPGA的CameraLink转SFP光口方案:Aurora 8B10B实现长距离图像传输 2026/9/9 10:37:33

基于FPGA的CameraLink转SFP光口方案:Aurora 8B10B实现长距离图像传输

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →
技能提升的底层逻辑:从刻意练习到核心技能构建 2026/9/9 10:37:33

技能提升的底层逻辑:从刻意练习到核心技能构建

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →
工业串口设备远程联网方案:蒲公英工业路由器与串口服务器实战 2026/9/9 10:37:33

工业串口设备远程联网方案:蒲公英工业路由器与串口服务器实战

1. 现场诊断:为什么很多工控串口设备迟迟没联网1.1 痛点来源:串口设备、老旧PLC、现场环境跑过工厂和户外现场的人应该都有印象,配电柜、水泵房、环保监测站、路灯控制箱这些地方,设备其实不算旧,但网络条件往往还停留…

阅读更多 →
港大开源AI学习系统:Agent架构与知识库错题集闭环 2026/9/9 10:37:33

港大开源AI学习系统:Agent架构与知识库错题集闭环

凌晨一点,你对着屏幕上的几百页PDF讲义,手里攥着错题本上红笔圈出的十几个反复出错的题型,想找一套“能记住我上周哪里不会”的工具。普通的AI问答帮你查一道题可以,但你问它“我最近一周在微积分上反复卡壳的是哪类题”&#xff…

阅读更多 →
计算机单片机毕设实战-基于 STM32 的 Hx711 体重采集与超声波身高检测系统设计 基于 STM32 的 OLED 显示 BMI 体征与 ESP‑01S 远程控制系统设计(013707) 2026/9/9 10:34:32

计算机单片机毕设实战-基于 STM32 的 Hx711 体重采集与超声波身高检测系统设计 基于 STM32 的 OLED 显示 BMI 体征与 ESP‑01S 远程控制系统设计(013707)

博主介绍:✌️码农一枚 ,专注于大学生项目实战开发、讲解和毕业🚢文撰写修改等。全栈领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于嵌入式单片机,Java、小程序技术领域和毕业项目实战 ✌️…

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

联系尧图顾问,获取一对一建站咨询

立即免费咨询 📞 400-888-8888
📞