首页 / 资讯中心 / 文章详情

AMI BIOS源代码开发实战:从代码结构到安全烧录

AMI BIOS源代码开发实战:从代码结构到安全烧录 ★ FEATURED ARTICLE
简介AMI BIOS源代码包提取自《BIOS研发技术剖析》配套光盘面向BIOS研发工程师、系统底层开发人员及希望深入理解计算机启动原理的学习者。资源共包含1294个文件压缩包仅5.61MB以asm汇编源码、inc头文件、asl/aml ACPI表、obj目标文件及mak构建脚本为主辅以bin固件镜像、exe工具与调试日志文件目录结构清晰。已有1078人学习下载。通过阅读源码可系统掌握POST开机自检流程、中断向量与INT 10h/INT 13h等BIOS调用实现、PCI设备与I/O端口初始化、CMOS配置读写、引导加载器选择逻辑以及传统BIOS向UEFI过渡的代码痕迹同时可学习硬件驱动编写、底层调试手段和ACPI电源管理模块的设计思路是理解BIOS内部机制和进行定制开发的实用参考资料。1. AMI BIOS源代码拿到手别急着烧先看懂这张代码地图某开发者接手一块停产主板的定制需求要改开机Logo、还要调整默认启动顺序。手里只有现成bin文件的时候每改一次都像开盲盒烧进去点不亮返工三回还得靠编程器刷回原厂才缓过来。直到拿到这份AMI BIOS源代码工程和配套光盘才第一次觉得是在“开发固件”不是在赌运气。整套资源以AMI BIOS的实现为主线覆盖POST流程、内存初始化、ACPI、SMM 与启动管理光盘里还带了完整的编译工具链和参考文档。适合主板固件工程师、BSP开发者以及那些想拆开启动黑匣子的嵌入式老手新手跟着完整编译一遍也能把PEI、DXE、BDS这几个阶段的概念彻底串起来。2. 先摸清AMI BIOS的骨架代码树、模块与构建体系2.1 顶层代码树CORE、SDK、OEM 三层不能改混拿到源码第一件事不是找main函数BIOS没有main。这套基于AMI Core 8体系的代码树顶层通常分成三大区域。CORE目录放的是平台无关的EFI核心实现包含PEI、DXE、BDS三个阶段的调度骨架SDK目录是芯片组和板卡相关的驱动包南桥、CPU微码、板载网卡、SATA控制器的初始化全在这一层OEM目录才是定制的主战场开机Logo、默认启动项、CMOS默认值、SMBIOS信息都在这层。实际展开后大概长这样。Source/ ├─ CORE/ │ ├─ CORE_DXE_SERVER/ # DXE核心驱动分发与协议注册 │ ├─ CORE_PEI_SERVER/ # PEI阶段CPU/内存早期初始化 │ └─ CORE_BDS/ # 启动设备选择BootOrder相关逻辑 ├─ SDK/ │ ├─ CHIPSET/ # 南桥/北桥寄存器定义与驱动 │ ├─ CPU/ # 微码加载、Cache与内存控制器 │ └─ DRIVERS/ # 板载设备驱动网卡、Audio等 └─ OEM/ ├─ LOGO/ # 开机Logo图片与转换脚本 ├─ SETUP/ # Setup界面字符串与默认值 └─ SMBIOS/ # 主板型号、序列号等表数据这段目录结构对应了BIOS开发里三个明确的责任边界。CORE层一般不动除非你在修一个跨平台通用的启动BugSDK层依赖具体芯片组换板卡时得整套同步更新OEM层则是每块主板差异化的地方定制需求九成在这层完成。拿到代码后先确认你的板卡对应哪个OEM目录很多人翻车都是因为改错了一份平台目录编译能过烧进去却不生效。这三层之间不是靠随意include来联动的而是通过Protocol协议和PCD平台配置参数传递数据。直接跨层引用内部头文件的话下一次构建会冒出一堆未定义符号排错最浪费时间的就是这类错误。所以我的习惯是改代码前先看目录归属OEM层能解决的绝不去碰CORE层。2.2 三段执行阶段与入口函数从PEI到BDS的路标BIOS从上电到进入操作系统代码执行分三个阶段源码里分别对应不同的Server目录理解这三个阶段的入口是后续所有排错的地图。PEI阶段在Cache As RAM里运行负责CPU微码加载、内存控制器训练和主内存探测DXE阶段从Flash解压驱动建立Protocol服务PCI枚举和ACPI表填充都发生在这里BDS阶段拿到DXE枚举出的设备列表按BootOrder变量挑一个启动项执行。找入口函数的方法比较固定每个模块的.inf文件里声明一个EntryPoint。例如CORE_BDS下面的BdsDxe.inf入口一般指向BdsEntry。想跟一条完整的启动流程从这个函数开始单步往下走最直接。我在面对一份不熟悉的代码树时会先把所有模块的入口函数列成清单按函数名猜职责这比在海量C文件里瞎翻快得多。阶段典型模块入口函数核心职责PEIPeiMemoryInitMemoryInitEntry内存控制器训练与内存探测PEICpuPeimCpuPeimEntry微码加载DXEPciHostBridgePciHostBridgeEntryPCI总线枚举DXEAcpiTableAcpiInitACPI表生成与发布BDSBdsDxeBdsEntry启动项选择与引导这张表是“源码路标”不是最终目标。真正要改的逻辑往往在入口函数下面第三四层但入口名能帮你快速避开无关模块直接锚定到目标区域。比如你只改默认启动顺序顺着BdsEntry进就行完全不用碰内存初始化那套东西。2.3 光盘工具链与路径约束别自作主张改目录配套光盘的核心价值之一是它带了完整的编译依赖。典型内容包括编译器常见VC或Intel编译器、Nasm汇编器、GenFw镜像封装工具以及AMI自己的批处理构建框架。光盘根目录一般有个Setup脚本用于配置环境变量构建统一从Build.bat入口调用。第一次编译前先确认工具链装到了哪路径问题会在后面频繁找你麻烦。cd C:\AMIBIOS\Source Build.bat /nologo /c /b /v /s这是典型的全量构建入口。/nologo去掉编译器版权横幅让日志干净一些/c指定C编译器后端/b表示生成最终FD镜像缺了它你只会得到中间产物拿不到可烧录的ROM文件/v输出详细日志首次构建建议必带/s跳过交互式确认。参数顺序在AMI的构建框架里不敏感但/b一定不能漏这是很多人第一次构建后找不到bin文件的原因。AMI构建脚本大量使用硬编码相对路径光盘默认的安装布局是C:\AMIBIOS。装到带空格或中文的目录里构建脚本会无提示地跳过部分模块最终镜像缺了东西烧录后症状千奇百怪。第一次全量构建大概需要半小时到一小时磁盘预留出5GB比较稳妥。增量编译则只重编改动的模块和依赖项能压到一两分钟适合后续频繁调试。2.4 源码模块与固件二进制的映射从GUID反查模块编译产物不是单个大文件而是几十上百个Module各自带GUID和FFS头打包后进入Firmware Volume。运行时日志里报了某个模块出错想回源码定位就得靠模块的GUID反查。AMI配套的MMTool工具能做这件事。MMTool.exe 打开ROM镜像 → 选择Firmware Volume → 按GUID搜索模块 → 记录模块名如BdsDxe与文件GUID搜到GUID后去源码树里搜索同名字段就能定位到具体C文件。这套映射关系是排障的基础没有它日志报错和源码之间就是断的。之前某次调试卡在内存初始化日志里反复出现PeiMemoryInit模块名靠GUID反查到SDK层才发现是一个Cache变量配置和CORE层的预期不符。没有MMTool时也可以靠构建日志反查。每次构建会把生成的模块名和GUID写进Build.log用文本工具检索模块名就能确认它有没有被编进镜像。findstr /i BdsDxe Build.log这条命令在Windows下检索日志里的模块名只看一件事目标模块是否真的参与了构建。很多情况是优化编译时链接器把未被引用的模块整个裁掉了源码里完全看不出删除痕迹唯一依据就是构建日志里有没有对应模块名以及fdf文件里有没有对应的FV引用行。养成看日志的习惯后这类“改了不生效”的玄学问题会减少大半。3. 搭一个最小可编译工程dsc、fdf 与首次烧录验证3.1 dsc与fdf各管什么先读文件头再改代码构建的配置核心是两对关键文件。dsc决定“编译什么”fdf决定“烧录在Flash的哪里”。拿到一套代码先别急着改功能把这两份文件头几十行读明白能避开后面一半的坑。很多定制需求最后出问题不是C代码写错而是配置文件和代码不一致。关键配置项集中在这几个字段先对着自己板卡的实际情况核对一遍。Platform.dsc PLATFORM_NAME 与OEM目录名保持一致 FLASH_SIZE 主板SPI Flash实际大小如8MB/16MB BOOT_DEVICE_DEFAULT 默认启动设备优先级 Platform.fdf FV_MAIN_SIZE 主Firmware Volume大小 BIOS_SIZE_OFFSET 镜像在Flash中的起始偏移FLASH_SIZE填小程序没有超容量的机会构建期直接报错填大却编译能过生成镜像可能覆盖SPI Flash上的其他区域例如管理引擎固件区。某开发者曾把16MB Flash配成32MB编译顺利烧录后主板直接停在CPU早期初始化最后用编程器刷回原厂备份才救回来。这类错误最隐蔽因为二进制工具看不到任何异常提示。配置项所在文件典型值参考错误后果FLASH_SIZEdsc0x100000016MB填大可能覆盖ME区域导致点不亮FV_MAIN_SIZEfdf0x200000过小导致模块链接失败BOOT_DEVICE_DEFAULTdsc与启动顺序表一致默认启动项异常BIOS_SIZE_OFFSETfdf0x0偏移错误导致镜像解压失败改配置文件前我习惯先做一次主板原厂备份的完整解析确认Flash总大小和实际分区布局然后把dsc里的FLASH_SIZE按原厂值照抄一步到位。不要凭感觉填数值这是后续所有烧录风险的第一道闸门。3.2 编译参数与增量编译首跑全量后续应增量首次构建建议用完整参数跑一遍把环境彻底验证通之后再增量编译才靠谱。命令本身和第二章的入口一致但首次运行时我更建议打开详细日志输出并且把日志存下来防止终端缓冲不够丢信息。cd C:\AMIBIOS\Source Build.bat /nologo /c /b /v /s build_log.txt 21这里 build_log.txt是把所有输出重定向到文件21把错误流合并进去。首次构建阶段日志文件比屏幕输出更可靠搜错也方便。构建系统是按依赖关系走的某些模块报错会连锁触发其他模块失败日志文件能让你从第一个真正的错误开始排查而不是盯着最后几屏红色输出头疼。增量编译更像提前买的后悔药只重编改动模块和依赖项几分钟就能拿到新镜像。注意增量编译要求命令参数和全量时保持一致换一个参数组合构建系统会认为配置已变重新走整体编译反而更慢。所以我的做法是把这行命令存成Build.bat每次只改源码不动命令。一个额外经验构建过程中如果出现假死现象先看是不是杀毒软件在扫描中间文件把源码目录加入白名单能省很多时间。BIOS构建会产生大量临时文件实时扫描的开销比想象中大。3.3 生成镜像后的第一轮验证备份、对比、再写入镜像生成后不要直接进烧录器。先把三个验证动作做完确认输出文件大小和预期一致用MMTool打开镜像确认关键模块GUID都在最后才是烧录。这样能拦截绝大多数低级配置错误。# 假设原厂备份为 factory.bin新镜像为 new.bin fc /b factory.bin new.binfc /b是Windows下的二进制文件对比命令输出第一处差异的字节偏移和内容。这里主要关心的不是全部差异而是镜像头部几百字节内有没有意外变化——那些区域通常属于平台描述和压缩表头一旦跑偏烧进去大概率点不亮。差异出现在尾部几十字节往往来自NVRAM变量区属正常现象。三道门槛都过了再拿刷新工具写入。第一次烧录前把原厂镜像完整备份到两个不同介质上是我现在的条件反射。备份文件命名里带上主板型号和日期防止几个月后认不出是哪块板的固件。这里补充一个容易踩的点刷新工具读Flash时默认可能只读BIOS区域而不是整个SPI Flash。备份时如果工具问区域范围选择Full或Entire Device确保把管理引擎区域一起读出来。只备份BIOS区域的“半份镜像”在回滚时常常不够用。4. 把一处启动逻辑改明白BootOrder、NVRAM 与回滚方案4.1 定位启动项管理模块从BdsEntry进入BootOrderBDS阶段的启动逻辑集中在BdsDxe模块入口函数BdsEntry。它做的事分两层从NVRAM读BootOrder变量确定设备排序然后按顺序尝试启动每一个BootOption。想改默认启动顺序最直接的位置是BootOrder变量的初始构造逻辑而不是运行时设备匹配那部分。EFI_STATUS BdsEntry ( IN EFI_BDS_PHASE_PROTOCOL *This ) { // 1. 从NVRAM读取BootOrder变量 Status GetVariable(LBootOrder, BootOrder); // 2. 遍历BootOption并按顺序启动 for (Index 0; Index BootOrderCount; Index) { BdsConnectDevice(BootOption[Index]); Status BdsStartBootImage(BootOption[Index]); } return EFI_SUCCESS; }上面的代码是BDS阶段的主干流程先读BootOrder再逐项连接设备并尝试引导。这里的逻辑说明两点一是BootOrder是NVRAM里的持久变量可能已存在并覆盖代码里的默认值二是启动项的描述字符串比如“USB Hard Drive”“Windows Boot Manager”在BootOption里定义跟BootOrder的顺序解耦。只改代码但不清NVRAM行为可能不变这是最常见的“改了没用”根源。4.2 修改默认启动顺序的代码路径与PCD联动源码里BootOrder的初值通常在一个名叫BootManager或DefaultBootOrder的模块里按设备类型拼接。常见做法是定义一张静态顺序表例如USB设备、SATA硬盘、NVMe设备、PXE网卡然后由构建系统把这张表编成默认的BootOrder变量。STATIC UINTN mDefaultBootOrder[] { BOOT_DEVICE_USB, // U盘优先 BOOT_DEVICE_SATA, // SATA硬盘 BOOT_DEVICE_NVME, // NVMe SSD BOOT_DEVICE_PXE, // 网卡PXE启动 };改这个数组的顺序即可调整出厂默认启动优先级。但要注意两点第一某些平台会同时用PCD控制首启设备改代码时要同步检查dsc里的BOOT_DEVICE_DEFAULT值两边不一致时构建系统可能以PCD为准你的代码改动等于白写第二NVRAM里如果已有旧BootOrder变量新默认值只在清空CMOS后才生效也就是进入Setup把设置恢复默认或短接CMOS跳线。这块板卡当时就遇到了类似情况代码里的顺序表已经改成U盘优先但烧录后启动还是从SATA硬盘走。排查到最后发现NVRAM残留了旧变量。从那以后我每次改启动逻辑都顺手记录一条清CMOS前后各测一次行为差异一旦出现立刻能锁定是变量覆盖还是代码没编进去。4.3 烧录与回滚备份为什么不是可选项改启动顺序这类改动风险不在代码逻辑本身而在烧录环节。SPI Flash是分区域的BIOS区域之外还有管理引擎和GOP驱动区域。用不完整的镜像或错误参数写入可能连带清掉其他区域。回滚方案只有一个拿到源代码那一刻就立刻从主板上完整读出原厂镜像。# 使用主板原厂工具读取整个SPI Flash afuwin64.exe -D factory_full.binafuwin是常见AMI刷新工具族里的程序-D参数表示Dump整个Flash到文件。如果工具不支持当前芯片组就得用编程器夹子离线读取。备份文件存两份放在不同位置之后所有调试都以这份文件作为基线。回滚时同样用完整镜像写回只写BIOS区域而不带其他分区点不亮的概率很高。这个习惯救过我不止一次。某次调试ACPI表时把DSDT写坏系统卡在POST后面黑屏当时原厂备份就在手边五分钟内刷回完全没有返工焦虑。从那以后刷写类操作在我这里永远遵循一个顺序先读备份再改代码最后烧录。备份不是安全建议是强制前置步骤。5. BIOS开发常踩的坑现象、原因与解决这一章整理的是多次构建和烧录实战中反复遇到的高频问题每条都按“现象→原因→解决”展开。源码光盘里的工程相对完整但环境的差异会让同一套代码在不同机器上跑出完全不同的结果。5.1 编译报错头文件路径不匹配现象构建推进到某个模块时终端打出cannot open include file或No such file or directory附带路径指向一个不存在的目录。原因两个高发点。一是拖动代码时改了目录名但dsc里的include路径没有同步缺哪一个头文件会有明确提示二是工具链安装路径带了空格或中文报错看起来像随机故障时而编过时而缺头文件实际是脚本拼接出的路径在某条命令行下被拆断。解决先全局搜索所有.dsc和.fdf里的include路径把目录名同步为实际值。工具链路径统一改成英文字符并保持相对路径写法。我习惯在一开始就把所有路径改成$(PLATFORM)宏开头的相对表达避免换一台机器、换一个盘符后全盘爆炸。改完后重新构建确认错误是否从第一个真正失败的模块开始报而不是后续连锁错误。5.2 烧录后点不亮现象刷新完固件重启主板无显示诊断卡或DEBUG卡停在某个早期代码常见是CPU初始化阶段不再往前走。原因三个高频来源。FLASH_SIZE配大覆盖了管理引擎区域fdf里的FV偏移不对镜像写在Flash里但解压地址错误部分模块被链接器裁掉DXE阶段缺了必要驱动导致启动流程中断。解决先回到第3章的验证动作比对镜像头部的关键字节并用MMTool确认关键模块GUID都在。如果配置无误再翻构建日志查被裁掉模块的依赖关系恢复引用。最稳妥的后悔药还是原厂备份出问题直接写回去不要在现场反复换参数试烧那只会放大损坏范围。5.3 ACPI表尺寸超限引发启动变慢现象系统能启动但明显变慢Windows事件日志出现ACPI错误MCTable或DSDT表校验失败。原因往ACPI表里加了自定义DSDT内容却没有同步调整fdf中专用于ACPI表的区域尺寸。ACPI区域在Flash里是独立分配的数据超限后表内容被截断操作系统解析时读到的表不完整启动阶段就会反复重试。解决在fdf中找到ACPI区域定义按新增内容体积调大尺寸同时确保总Flash布局不溢出。改完重新构建用MMTool打开镜像核对DSDT和SSDT是否完整。给表格扩容后启动慢和ACPI报错通常会同时消失这两个症状往往指向同一个根因。5.4 调试串口无输出现象代码里加了一堆DebugPrint串口终端却什么都没有改动看起来完全没有执行。原因三个排查方向。串口重定向没有在dsc里打开常见是调试输出宏被置位关闭串口的I/O基地址与主板实际UART不一致打印点在串口驱动加载前就执行了数据被直接丢弃。解决先确认dsc里的串口配置开关和基地址与主板原理图一致再在BdsEntry之前留一个基准打印点做对照。把打印语句挪到串口驱动加载之后很多莫名消失的输出就回来了。对于PEI阶段的早期输出则要看平台是否支持SPI调试或ITP这类硬件调试方案。5.5 OEM目录改动不生效现象OEM目录下某个模块改了编译成功烧录后行为和原来一模一样。原因最常见的是模块根本没被编进镜像。优化编译会裁掉未引用的模块或者fdf中模块对应的FV路径写错实际加载的是另一份旧版本。另一个常见原因是NVRAM变量残留覆盖了代码里的默认值。解决先用findstr在Build.log里确认目标模块名是否出现再核对fdf里对应模块的行是否指向正确路径。两者都没问题就清空CMOS后重新测试。这套排查顺序从构建产物到运行数据一层层排除基本能定位所有“改了不生效”的情况。6. 进阶技巧用源码级打印与诊断卡验证你的改动6.1 在BDS阶段插入自定义输出验证启动顺序改动最好的手段是在BdsEntry里加一条DebugPrint把每次尝试启动的BootOption路径打出来串口就能看到实际走了哪个设备不用再猜。DEBUG((DEBUG_INFO, Attempt boot: %s\n, BootOption[Index]-Description));这条打印把设备的描述字符串输出到调试串口。烧录后如果看到USB设备优先于SATA设备说明顺序表改对了如果顺序还是原样则按第5章的排查路径去查NVRAM残留和模块裁剪。6.2 用诊断卡和逻辑分析仪交叉验证DebugPrint能覆盖DXE和BDS阶段但更早的PEI阶段可能需要诊断卡或硬件调试器配合。手边放一块POST诊断卡主板停在哪一步会显示对应代码和日志里的模块序号能对上。逻辑分析仪则用于验证SPI Flash的片选和读时序是否异常配置错误时能看到读写地址跳变这比纯看日志直观得多。6.3 从源码注释反推芯片组寄存器意图源码的价值不只在改还在于它是芯片组手册的导读。很多寄存器配置代码的注释会直接写设计意图例如某段Cache配置旁边标注了功耗与延迟的取舍。把注释和数据手册对照起来读能更快定位“某个值为什么不推荐却默认使用”这类问题。从那以后我每次拿到新板卡都强制先走一遍原厂备份、全量构建、日志检查、烧录验证这四步再开始碰功能代码。哪怕只是改一个启动顺序这样的小改动也按全流程走。回头来看前期多花的几小时换来了后续调试的大量返工时间。如果你手里恰好有这套带光盘的AMI BIOS源代码资源建议先按这条路径走一遍再动代码省下来的时间远比预想的多。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?
咨询建站