UEFI开发探索98 – 硬盘访问Diskdump 您所在的位置:网站首页 读取sata硬盘 UEFI开发探索98 – 硬盘访问Diskdump

UEFI开发探索98 – 硬盘访问Diskdump

2024-07-01 07:46| 来源: 网络整理| 查看: 265

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

硬盘访问Diskdump 1 UEFI的存储介质访问栈2 编写Diskdump程序2.1 Block I/O简介1)设备信息Media2)读扇区函数ReadBlocks3)写扇区函数WriteBlocks3)更新介质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的存储介质访问栈 图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 is responsible 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中的MediaID IN 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 is responsible 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中的MediaID IN 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

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

[Protocols] gEfiSimpleTextInputExProtocolGuid gEfiSimplePointerProtocolGuid gEfiGraphicsOutputProtocolGuid gEfiSimpleFileSystemProtocolGuid gEfiDevicePathProtocolGuid gEfiBlockIoProtocolGuid # 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; //unsupport nBlockIO = HandleCount; //保存BlockIO数目 if(HandleCount>250) HandleCount = 250; //只支持250个存储设备,应该不大可能有这么多 for (HandleIndex = 0; HandleIndex 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 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 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


【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有