(请保留-> 作者: 罗冰 https://blog.csdn.net/luobing4365)

硬盘访问Diskdump

  • 1 UEFI的存储介质访问栈
  • 2 编写Diskdump程序
    • 2.1 Block I/O简介
      • 1)设备信息Media
      • 2)读扇区函数ReadBlocks
      • 3)写扇区函数WriteBlocks
      • 3)更新介质FlushBlocks
    • 2.2 Diskdump编程
      • 1)项目中增加对Block I/O的支持
      • 2)实现对Block I/O实例的获取
      • 3)实现功能
  • 3 测试

之前在CSDN上建了一个专栏,名字为汇编语言探索,准备聊一些用汇编写的小项目。大概写了十几篇关于Foxdisk运行原理的文章,后面还会不定期的写写,特别是最近看到一个俄罗斯程序员用FASM写的KolibriOS,贼有意思了,找时间读一读。

Foxdisk本来是我为自己写的一个多操作系统引导的软件,类似于Grub。核心点在于,在Foxdisk内可以将硬盘分成若干区域,每个区域装一个操作系统,各操作系统间可以通过共享的分区分享数据。而提供的图形界面,比单调的Grub让我舒服些。

这是一个很好的想法,能满足我使用多个操作系统的开发习惯。Foxdisk开发了3代,前两代是纯粹用汇编语言写的,3.0则使用了C语言嵌汇编。再然后,我迷上了UEFI,对于在Legacy BIOS下开发的Foxdisk,就这么抛弃了。

Foxdisk 3.0中,内置了对硬盘分区的功能(相当于Fdisk或spfdisk之类的工具)。实际上,Foxdisk大部分的工作,都是在和硬盘打交道。

进入UEFI后,一直不怎么想去研究硬盘访问。总觉得UEFI本身已经将硬盘访问封装得很好了,没什么必要再去开发分区、格式化之类的软件了。

刚好在近期的项目中,需要在Option ROM中查看某几个扇区的数据。为了比对Option ROM的代码,需要开发一个UEFI小程序,实现类似功能。

1 UEFI的存储介质访问栈

UEFI规范中提供了大量的存储介质Protocol(Media Protocol),甚至包括内存虚拟盘(RAM Disk)都支持了。

Legacy BIOS下是通过Int 0x13进行硬盘访问的,由寄存器AH标识各种功能号,供程序员调用。习惯了这种思维,再看UEFI的访问方式,总是有点别扭。

首先需要找出类似功能的访问接口,UEFI提供了从硬盘协议层的Protocol,一直到文件系统的Protocol,非常完善,其架构如图1所示。

图1 UEFI的存储介质访问栈

PassThrough以接近介质访问协议的方式提供接口,包括ATA协议、SCSI协议等,因此能提供相当复杂的操作。

在此之上,UEFI提供了Block I/O和Block I/O 2,可以按扇区(块)读写设备。Block |/O是阻塞操作,Block I/O为异步操作。这一层的操作,类似于Legacy BIOS中的Int 0x13的按扇区访问操作。

Disk I/O和Disk I/O 2可以从任意偏移处读写磁盘,并且可以读写任意字节数,相比于Block I/O和Block I/O 2只能按扇区读写,更为灵活。类似的,Disk I/O是阻塞操作,而Disk I/O 2是异步操作。

再往上所构建的Protocol,是可以操作FAT文件系统的Simple File System和访问文件的File。之前的博客中,使用File Protocol构建了访问文件的读写函数(FileRW.c)。所构建的函数,与C语言库中的fread()、fwrite()等函数类似。

为了实现篇首所说的访问指定扇区的小程序,最合适的是Block I/O,下面介绍编写过程。

2 编写Diskdump程序

2.1 Block I/O简介

与Block I/O Protocol相关的函数及GUID,定义在MdePkg\Include\Protocol\BlockIo.h中。其结构体为:

struct _EFI_BLOCK_IO_PROTOCOL {UINT64              Revision;EFI_BLOCK_IO_MEDIA  *Media;EFI_BLOCK_RESET     Reset;EFI_BLOCK_READ      ReadBlocks;EFI_BLOCK_WRITE     WriteBlocks;EFI_BLOCK_FLUSH     FlushBlocks;
};

它提供了四个接口函数:Reset、ReadBlocks、WriteBlocks和FlushBlocks,以及版本号Revision和介质属性Media。

1)设备信息Media

Media指向设备的结构体EFI_BLOCK_IO_MEDIA,它包含了设备的相关属性信息。其内容如下:

/**Block IO read only mode data and updated only via members of BlockIO
**/
typedef struct {/// The curent media Id. If the media changes, this value is changed.UINT32  MediaId;         /// TRUE if the media is removable; otherwise, FALSE.  BOOLEAN RemovableMedia;/// TRUE if there is a media currently present in the device;/// othersise, FALSE. THis field shows the media present status/// as of the most recent ReadBlocks() or WriteBlocks() call.  BOOLEAN MediaPresent;/// TRUE if LBA 0 is the first block of a partition; otherwise/// FALSE. For media with only one partition this would be TRUE.BOOLEAN LogicalPartition;/// TRUE if the media is marked read-only otherwise, FALSE./// This field shows the read-only status as of the most recent WriteBlocks () call.BOOLEAN ReadOnly;/// TRUE if the WriteBlock () function caches write data.BOOLEAN WriteCaching; /// The intrinsic block size of the device. If the media changes, then/// this field is updated.  UINT32  BlockSize; /// Supplies the alignment requirement for any buffer to read or write block(s).UINT32  IoAlign; /// The last logical block address on the device./// If the media changes, then this field is updated. EFI_LBA LastBlock; /// Only present if EFI_BLOCK_IO_PROTOCOL.Revision is greater than or equal to/// EFI_BLOCK_IO_PROTOCOL_REVISION2. Returns the first LBA is aligned to /// a physical block boundary. EFI_LBA LowestAlignedLba;/// Only present if EFI_BLOCK_IO_PROTOCOL.Revision is greater than or equal to/// EFI_BLOCK_IO_PROTOCOL_REVISION2. Returns the number of logical blocks /// per physical block.UINT32 LogicalBlocksPerPhysicalBlock;/// Only present if EFI_BLOCK_IO_PROTOCOL.Revision is greater than or equal to/// EFI_BLOCK_IO_PROTOCOL_REVISION3. Returns the optimal transfer length/// granularity as a number of logical blocks.UINT32 OptimalTransferLengthGranularity;
} EFI_BLOCK_IO_MEDIA;

一般来说,BlockSize是0x200,也即512字节一个扇区(块)。

UEFI完全摒弃了以前CHS的地址模式,直接用LBA地址模式来标志扇区地址。LBA地址从0计算,最后一个地址为LastBlock。

2)读扇区函数ReadBlocks

ReadBlocks用于读取块设备,也即按扇区进行读取,其函数原型如下所示。

/**Read BufferSize bytes from Lba into Buffer.@param  This       Indicates a pointer to the calling context.@param  MediaId    Id of the media, changes every time the media is replaced.@param  Lba        The starting Logical Block Address to read from@param  BufferSize Size of Buffer, must be a multiple of device block size.@param  Buffer     A pointer to the destination buffer for the data. The caller isresponsible for either having implicit or explicit ownership of the buffer.@retval EFI_SUCCESS           The data was read correctly from the device.@retval EFI_DEVICE_ERROR      The device reported an error while performing the read.@retval EFI_NO_MEDIA          There is no media in the device.@retval EFI_MEDIA_CHANGED     The MediaId does not matched the current device.@retval EFI_BAD_BUFFER_SIZE   The Buffer was not a multiple of the block size of the device.@retval EFI_INVALID_PARAMETER The read request contains LBAs that are not valid, or the buffer is not on proper alignment.**/
typedef
EFI_STATUS
(EFIAPI *EFI_BLOCK_READ)(IN EFI_BLOCK_IO_PROTOCOL          *This,      // EFI_BLOCK_IO_PROTOCOL实例IN UINT32                         MediaId,    //*Media中的MediaIDIN EFI_LBA                        Lba,        //读取设备(或分区)的LBA地址IN UINTN                          BufferSize, //读取的字节数,为BlockSize整数倍OUT VOID                          *Buffer     //存储数据的缓冲区);

3)写扇区函数WriteBlocks

WriteBlocks用于按快写设备(或扇区),其入口参数与ReadBlocks相同,如下所示:

/**Write BufferSize bytes from Lba into Buffer.@param  This       Indicates a pointer to the calling context.@param  MediaId    The media ID that the write request is for.@param  Lba        The starting logical block address to be written. The caller isresponsible for writing to only legitimate locations.@param  BufferSize Size of Buffer, must be a multiple of device block size.@param  Buffer     A pointer to the source buffer for the data.@retval EFI_SUCCESS           The data was written correctly to the device.@retval EFI_WRITE_PROTECTED   The device can not be written to.@retval EFI_DEVICE_ERROR      The device reported an error while performing the write.@retval EFI_NO_MEDIA          There is no media in the device.@retval EFI_MEDIA_CHNAGED     The MediaId does not matched the current device.@retval EFI_BAD_BUFFER_SIZE   The Buffer was not a multiple of the block size of the device.@retval EFI_INVALID_PARAMETER The write request contains LBAs that are not valid, or the buffer is not on proper alignment.**/
typedef
EFI_STATUS
(EFIAPI *EFI_BLOCK_WRITE)(IN EFI_BLOCK_IO_PROTOCOL          *This,      // EFI_BLOCK_IO_PROTOCOL实例IN UINT32                         MediaId,    //*Media中的MediaIDIN EFI_LBA                        Lba,         //读取设备(或分区)的LBA地址IN UINTN                          BufferSize,   //要写的字节数,为BlockSize整数倍OUT VOID                          *Buffer     //数据的缓冲区,写往设备(或分区));

3)更新介质FlushBlocks

写往设备的数据,在实际写入设备前,函数就会返回EFI_SUCCESS,数据实际上存储在缓存中.此函数将设备缓冲中修改过的数据,全部更新到介质中。其函数原型为:

/**Flush the Block Device.@param  This              Indicates a pointer to the calling context.@retval EFI_SUCCESS       All outstanding data was written to the device@retval EFI_DEVICE_ERROR  The device reported an error while writting back the data@retval EFI_NO_MEDIA      There is no media in the device.**/
typedef
EFI_STATUS
(EFIAPI *EFI_BLOCK_FLUSH)(IN EFI_BLOCK_IO_PROTOCOL  *This    // EFI_BLOCK_IO_PROTOCOL实例);

2.2 Diskdump编程

了解了Block I/O的函数接口后,可以进入实质的编程阶段。

所实现的Diskdump程序,主要实现两个功能:
1) 获取当前系统下有多少个BlockIo实例,显示每个BlockIo设备的信息;
2) 指定BlockIo设备以及其LBA地址,得到扇区数据并显示在屏幕上。

编程步骤如下:

1)项目中增加对Block I/O的支持

添加头文件的包含:

#include <Protocol/BlockIo.h>

并在INF文件的[Protocols]部分,增加对应的GUID声明:

[Protocols]gEfiSimpleTextInputExProtocolGuid            gEfiSimplePointerProtocolGuidgEfiGraphicsOutputProtocolGuidgEfiSimpleFileSystemProtocolGuidgEfiDevicePathProtocolGuidgEfiBlockIoProtocolGuid   # add for BlockIO robin 20210824

2)实现对Block I/O实例的获取

实现方法和其他Protocol实例的获取方法一样,如下:

EFI_BLOCK_IO_PROTOCOL* gBlockIoArray[256];
UINTN nBlockIO = 0;
EFI_STATUS LocateBlockIO(void)
{EFI_STATUS                         Status;EFI_HANDLE                         *BlockIOHandleBuffer = NULL;UINTN                              HandleIndex = 0;UINTN                              HandleCount = 0;//get the handles which supports Status = gBS->LocateHandleBuffer(ByProtocol,&gEfiBlockIoProtocolGuid,NULL,&HandleCount,&BlockIOHandleBuffer);if (EFI_ERROR(Status)) return Status;      //unsupportnBlockIO = HandleCount;     //保存BlockIO数目if(HandleCount>250)HandleCount = 250; //只支持250个存储设备,应该不大可能有这么多for (HandleIndex = 0; HandleIndex < HandleCount; HandleIndex++){Status = gBS->HandleProtocol(BlockIOHandleBuffer[HandleIndex],&gEfiBlockIoProtocolGuid,(VOID**)&(gBlockIoArray[HandleIndex]));if (EFI_ERROR(Status))    break;else{Status = EFI_SUCCESS;}}if(BlockIOHandleBuffer!=NULL)FreePool(BlockIOHandleBuffer);return Status;
}

3)实现功能

具体的实现,在main()函数中。根据不同的命令行参数,来执行相应的动作。实现代码如下:

if(Argc == 1)  //列出所有BlockIO设备
{Print(L"BlockIO counts: %d\n",nBlockIO);for(i=0; i<nBlockIO; i++){Print(L" Number %02d:\n",i);dumpBlockIOMedia(gBlockIoArray[i]);WaitKey();}
}
else if(Argc == 2)
{if((strcmp("-h",Argv[1])==0) ||(strcmp("-H",Argv[1])==0) ||  (strcmp("-?",Argv[1])==0)){Print(L"Syntax: Diskdump x y\n");Print(L"  x: number of BlockIO\n");Print(L"  y: LBA Address\n");}
}
else if(Argc == 3)
{EFI_STATUS Status;sscanf(Argv[1],"%d",&number);sscanf(Argv[2],"%lld",&rAddress);Print(L"Get data from: BlockIo[%x],LBA-%ld\n",number,rAddress);if(number > (UINT16)nBlockIO){Print(L"Error: Out of range!\n");}else{Status = gBlockIoArray[number]->ReadBlocks(gBlockIoArray[number],gBlockIoArray[number]->Media->MediaId,rAddress,512,Buffer);if(EFI_ERROR(Status))Print(L"%r\n",Status);else{Print(L"--------0--1--2--3--4--5--6--7--8--9--A--B--C--D--E--F-\n");for(i=0; i<32; i++){if(i==16){WaitKey();Print(L"--------0--1--2--3--4--5--6--7--8--9--A--B--C--D--E--F-\n");}Print(L"0x%03x:  ",i*16);gST->ConOut->SetAttribute(gST->ConOut,EFI_BACKGROUND_RED|EFI_WHITE);for(j=0; j<16; j++){      Print(L"%02x",Buffer[i*16+j]);if(j<15)Print(L" ");}gST->ConOut->SetAttribute(gST->ConOut,EFI_BACKGROUND_BLACK|EFI_LIGHTGRAY);Print(L"\n");}}}
}

分为三种情况:
1) 无参数时,打印所有Block I/O设备的属性(*Media);
2) 一个参数时,只接受“-h”、“-H”和“-?”三个输入,会打印基本的语法说明;
3) 两个参数时,认为第一个参数时Block I/O的序号(从0开始),第二个参数是LBA地址。

两个参数时,会将获取到的扇区数据打印出来。

3 测试

在模拟器上没法测试,只能在实际机器上进行测试。使用如下命令编译:

C:\vUDK2018\edk2>build -p RobinPkg\RobinPkg.dsc -m RobinPkg\Applications\Diskdump\Diskdump.inf -a X64

我所测试的机器,带有一个M.2硬盘,进入UEFI Shell会发现4个设备,前两个是硬盘和U盘设备,后两个是它们的分区。

获取第1个设备的LBA 0数据,结果如图2所示。

图2 获取BlockIO 0,LBA 0的数据

出现了熟悉的MBR信息,很容易看出,硬盘上只分了一个分区。

UEFI Shell的一屏没法把512字节的数据全部打印出来,图2是由两张图拼接而成的。

如下为本篇项目的代码,可以编译试试。

Gitee地址:https://gitee.com/luobing4365/uefi-explorer
项目代码位于:/ FF RobinPkg/RobinPkg/Applications/Diskdump下


UEFI开发探索98 – 硬盘访问Diskdump相关推荐

  1. UEFI开发探索99 – UEFI Shell下截屏工具

    (请保留-> 作者: 罗冰 https://blog.csdn.net/luobing4365) UEFI Shell下截屏工具 1 PrintScreenLogger的代码结构 1)Print ...

  2. UEFI开发探索97 – EDK2模拟器搭建网络环境

    (请保留-> 作者: 罗冰 https://blog.csdn.net/luobing4365) EDK2模拟器搭建网络环境 1 搭建EDK2开发环境 1)工具安装 2)下载代码库 3)更新子模 ...

  3. UEFI开发探索85- YIE002USB开发板(08 制作HID设备)

    (请保留-> 作者: 罗冰 https://blog.csdn.net/luobing4365) YIE002USB开发板之制作HID设备-编程 1 YIE002-STM32的USB编程 2 调 ...

  4. UEFI开发探索QA – 问题辑录(持续更新)

    最近正在尝试在Unbutu16上搭建开发和调试环境,其中过程一言难尽,到现在也没完成到符合我要求的程度. 正是因为遇到障碍,我今天早上回到Win10+UDK2018的环境下,想重新编译下AppPkg, ...

  5. UEFI开发探索95 – 弹跳小游戏

    (请保留-> 作者: 罗冰 https://blog.csdn.net/luobing4365) UEFI下的弹跳小游戏 1 Bounce游戏 1.1 游戏架构 1.2 移植和编写代码 1)编写 ...

  6. UEFI开发探索02 – 环境搭建1

    (请保留->作者:罗冰 ) 开发初期的目的就是做出可以在pci rom上跑的Oprom,当然是在uefi bios下.我的计划大致如下: 1 搭建完整的编译环境,了解使用哪些库进行编译: 2 我 ...

  7. UEFI开发探索94 – 迷宫小游戏

    (请保留-> 作者: 罗冰 https://blog.csdn.net/luobing4365) UEFI下的迷宫小游戏 1 Maze程序结构分析 1)定义全局变量 2)设置迷宫 3) 游戏控制 ...

  8. UEFI开发探索100 – 《UEFI编程实践》发布啦

    (请保留-> 作者: 罗冰 https://blog.csdn.net/luobing4365) <UEFI编程实践>发布 1 内容简介 第一部分 UEFI环境搭建及UEFI应用构建 ...

  9. UEFI开发探索81- YIE002USB开发板(04 制作HID设备)

    (请保留-> 作者: 罗冰 https://blog.csdn.net/luobing4365) YIE002USB开发板之制作HID设备-USB系统概述 1 USB规范简介 2 软件工程师眼中 ...

最新文章

  1. 如何挑选深度学习 GPU?
  2. 【MATLAB】数据分析之数据插值
  3. 高性能MySQL之架构与历史(1)
  4. iOS开发 tabBarController选中状态
  5. ftp linux 开启验证_在linux中开启ftp服务
  6. 灵图天行者9 pc版_原神PC预下载现已开启
  7. flush python_带有示例的Python File flush()方法
  8. Linux内核的中断机制
  9. python-jieba分词模块
  10. Mysql学习总结(43)——MySQL主从复制详细配置
  11. 分布式系统认证方案_分布式系统认证需求_Spring Security OAuth2.0认证授权---springcloud工作笔记135
  12. 小程序多标签切换、Tab切换类似功能
  13. Banner设计文字如何排版,如何设计字体
  14. 节后上班 北京车辆尾号限行2日轮换
  15. 基于FPGA的Yolov4 tiny目标检测网络加速器
  16. 微型计算机原理与应用第三版王克义编著
  17. Kotlin 正则表达式
  18. 如何快速剪辑多个视频,将视频分段保存导出播放
  19. 配置了Maven环境变量后,cmd中mvn -v一直报“mvn不是内部命令”
  20. ESD的防护要求和器件注意事项

热门文章

  1. 纪念我的第一个程序员节
  2. 【备注接口】为什么那么多人开网店不赚钱?原因如下
  3. 流式低代码编程,拖拽节点画流程图并运行
  4. 在ue4的CBL中查询(函数、变量)的几点提示
  5. a标签download属性无效_html常用标签大全
  6. js刷新页面得重新加载和页面的刷新
  7. 星际酒馆反甲、吸血、毒质变、伤害buff的分析
  8. 浅谈:前端如何赋能业务?
  9. Imperva WAF 添加黑名单
  10. 2021年高处安装、维护、拆除免费试题及高处安装、维护、拆除模拟考试题库