UEFI Relocation 原理
作者:互联网
本文主要介绍的是UEFI Relocation的原理,以及主要的代码流程。
首先:为什么要重定位?
UEFI和传统的BIOS不一样,运行的特点也不一样,在编译阶段,每个模块都被编译成.efi的文件,然后将多个efi文件又生成Fv,最后又生成.fd文件,以这种方式存储在BIOS芯片中。在二进制的代码段中,每条指令的地址都是基于本模块的基址的一个偏移,然而每个模块真正运行的基地址,可能随着环境的不同这个基址也不同。当运行的时候,每个模块被加载到内存,这个内存的地址作为这个模块的基址,然后根据这个基址需要重新reloc每条指令的地址,这样才确定了每条指令运行的真正的虚拟地址。所以说,UEFI的编译原理决定了其必须进行重定位。
下面是本文主要参考的资料,主要来自ELF官方和PE32格式的官方资料,以及一篇介绍PE格式比较详细的博客。
下面是网站地址:
https://blog.csdn.net/junbopengpeng/article/details/40786291(博客地址)
https://dmz-portal.mips.com/wiki/MIPS_relocation_types#R_MIPS_HIGHER(标准ELF文件需要relocation的type)
https://docs.microsoft.com/en-us/windows/desktop/Debug/pe-format#intel-itanium-processor-family-ipf
上面的网站详细的介绍了elf和pe32+的格式,以及其relocated的主要原理,需要理解这些才能看懂本文后面讲的内容。
整个relocation过程,主要分为三部分,也可以说是三部分功能,对应的代码也是三处主要添加的地方。但是有两部分其实代码是可以复用的。下面就详细的介绍一下每一部分的修改和对应的原理。
第一部分:在编译阶段ELF文件 向PE32+转化的时候,对relocation段的修改:
其代码位于BaseTools/Source/C/GenFw中的Elf64Convert.c中。
这部分代码主要是在函数WriteRelocations64()中,这个函数主要就是ELF文件的relocation段向PE32+格式relocation段转化的时候需要进行的修改,不同的平台上需要的修改不同。在具体的平台如何修改还要参照具体平台的官网来写。
下面是添加的MIPS分之的代码:
else if (mEhdr->e_machine == EM_MIPS64EL) {
Elf_Sym *Sym = (Elf_Sym *)
(Symtab +ELF_R_SYM(Rel->r_info)*SymtabShdr->sh_entsize);
UINT64 Value = 0;
UINT16 Highest = 0;
INT16 Low = 0, High = 0, Higher = 0;
switch (ELF_R_TYPE(Rel->r_info)) {
case R_MIPS_32:
CoffAddFixup (
mCoffSectionsOffset[RelShdr->sh_info]
+ (Rel->r_offset - SecShdr->sh_addr),
EFI_IMAGE_REL_BASED_HIGHLOW
);
break;
case R_MIPS_26:
CoffAddFixup (
mCoffSectionsOffset[RelShdr->sh_info]
+ (Rel->r_offset - SecShdr->sh_addr),
EFI_IMAGE_REL_BASED_MIPS_JMPADDR
);
break;
case R_MIPS_HI16:
CoffAddFixup (
mCoffSectionsOffset[RelShdr->sh_info]
+ (Rel->r_offset - SecShdr->sh_addr),
EFI_IMAGE_REL_BASED_HIGHADJ
);
Value = Sym->st_value + ((Elf64_Rela*)Rel)->r_addend;
BreakValue(Value, &Low, &High, &Higher, &Highest);
CoffAddFixupEntry(Low);
CoffAddFixupEntry(Higher);
CoffAddFixupEntry(Highest);
break;
case R_MIPS_LO16:
CoffAddFixup (
mCoffSectionsOffset[RelShdr->sh_info]
+ (Rel->r_offset - SecShdr->sh_addr),
EFI_IMAGE_REL_BASED_LOWADJ
);
Value = Sym->st_value + ((Elf64_Rela*)Rel)->r_addend;
BreakValue(Value, &Low, &High, &Higher, &Highest);
CoffAddFixupEntry(High);
CoffAddFixupEntry(Higher);
CoffAddFixupEntry(Highest);
break;
case R_MIPS_64:
CoffAddFixup(
(UINT32) ((UINT64) mCoffSectionsOffset[RelShdr->sh_info]
+ (Rel->r_offset - SecShdr->sh_addr)),
EFI_IMAGE_REL_BASED_DIR64);
break;
case R_MIPS_HIGHER:
CoffAddFixup (
mCoffSectionsOffset[RelShdr->sh_info]
+ (Rel->r_offset - SecShdr->sh_addr),
EFI_IMAGE_REL_BASED_HIGHERADJ
);
Value = Sym->st_value + ((Elf64_Rela*)Rel)->r_addend;
BreakValue(Value, &Low, &High, &Higher, &Highest);
CoffAddFixupEntry(Low);
CoffAddFixupEntry(High);
CoffAddFixupEntry(Highest);
break;
case R_MIPS_HIGHEST:
CoffAddFixup (
mCoffSectionsOffset[RelShdr->sh_info]
+ (Rel->r_offset - SecShdr->sh_addr),
EFI_IMAGE_REL_BASED_HIGHESTADJ
);
Value = Sym->st_value + ((Elf64_Rela*)Rel)->r_addend;
BreakValue(Value, &Low, &High, &Higher, &Highest);
CoffAddFixupEntry(Low);
CoffAddFixupEntry(High);
CoffAddFixupEntry(Higher);
break;
case R_MIPS_NONE:
case R_MIPS_16:
case R_MIPS_REL32:
case R_MIPS_GPREL16:
case R_MIPS_LITERAL:
case R_MIPS_GOT16:
case R_MIPS_PC16:
case R_MIPS_CALL16:
case R_MIPS_GPREL32:
case R_MIPS_SHIFT5:
case R_MIPS_SHIFT6:
case R_MIPS_GOT_DISP:
case R_MIPS_GOT_PAGE:
case R_MIPS_GOT_OFST:
case R_MIPS_GOT_HI16:
case R_MIPS_GOT_LO16:
case R_MIPS_SUB:
case R_MIPS_INSERT_A:
case R_MIPS_INSERT_B:
case R_MIPS_DELETE:
case R_MIPS_CALL_HI16:
case R_MIPS_CALL_LO16:
case R_MIPS_SCN_DISP:
case R_MIPS_REL16:
case R_MIPS_ADD_IMMEDIATE:
case R_MIPS_PJUMP:
case R_MIPS_RELGOT:
case R_MIPS_JALR:
case R_MIPS_TLS_DTPMOD32:
case R_MIPS_TLS_DTPREL32:
case R_MIPS_TLS_DTPMOD64:
case R_MIPS_TLS_DTPREL64:
case R_MIPS_TLS_GD:
case R_MIPS_TLS_LDM:
case R_MIPS_TLS_DTPREL_HI16:
case R_MIPS_TLS_DTPREL_LO16:
case R_MIPS_TLS_GOTTPREL:
case R_MIPS_TLS_TPREL32:
case R_MIPS_TLS_TPREL64:
case R_MIPS_TLS_TPREL_HI16:
case R_MIPS_TLS_TPREL_LO16:
case R_MIPS_GLOB_DAT:
case R_MIPS_PC10:
case R_MIPS_PC21_S2:
case R_MIPS_PC26_S2:
case R_MIPS_PC18_S3:
case R_MIPS_PC19_S2:
case R_MIPS_PCHI16:
case R_MIPS_PCLO16:
case R_MIPS_COPY:
case R_MIPS_JUMP_SLOT:
break;
代码中每一个case的类型,是来自ELF官网中MIPS机器需要进行重定位的类型,每种类型如何转是在MIPS平台的PE32+格式如何重定位决定的。
下面是我们需要进行转换的relocation的类型:
R_MIPS_32在上面的官网上有定义,其值分别为18,转换到PE32+格式后,对对应的relocation type为IMAGE_REL_BASED_HIGHLOW,这个类型在后面PE32+格式 relocation的时候,采用的算法就是
在上面的网站中有介绍的。根据其算法的原理,这里需要我们将每一个relocation段中的内容全部都添加到对应的relocation entry 中。对应的代码就是:
CoffAddFixup (
mCoffSectionsOffset[RelShdr->sh_info]
+ (Rel->r_offset - SecShdr->sh_addr),
EFI_IMAGE_REL_BASED_HIGHLOW
);
CoffAddFixup的第一个参数中,mCoffSectionsOffset[RelShdr->sh_info]表示relocation段中的每一个section头中的表示的这个section所依赖的type。具体具体ELF格式的Section头,包含的内容如下:
/*
* Section header.
*/
typedef struct {
__Elf64_Word__sh_name;__/* Section name (index into the
__________ section header string table). */
__Elf64_Word__sh_type;__/* Section type. */
__Elf64_Xword_sh_flags;_/* Section flags. */
__Elf64_Addr__sh_addr;__/* Address in memory image. */
__Elf64_Off_sh_offset;__/* Offset in file. */
__Elf64_Xword_sh_size;__/* Size in bytes. */
__Elf64_Word__sh_link;__/* Index of a related section. */
__Elf64_Word__sh_info;__/* Depends on section type. */
__Elf64_Xword_sh_addralign;_/* Alignment in bytes. */
__Elf64_Xword_sh_entsize;_/* Size of each entry in section. */
} Elf64_Shdr;
上面的mCoffSectionsOffset[RelShdr->sh_info]就是获取每一个section头中的sh_info的值,然后加上(Rel->r_offset - SecShdr->sh_addr)的值,其中Rel->r_offset就是获取的section中每一个entry中需要relocation的地址与section头中记录的section在内存中的地址的偏移。将这个64位的地址作为第一个参数,第二个参数就是对应的PE32+格式下需要的relocation的type。这样调用函数CoffAddFixup()将整个Entry的数据写入到PE2+格式下的Entry中。注意每一个Entry中包含的需要relocation的字段的内容存储在下面的结构体中。r_offset表示需要relocation的地址,r_info表示所使用的relocation type.
/* Relocations that don't need an addend field. */
typedef struct {
__Elf64_Addr__r_offset;_/* Location to be relocated. */
__Elf64_Xword_r_info;___/* Relocation type and symbol index. */
} Elf64_Rel;
R_MIPS_64类型与上面的类似,只不过CoffAddFixup()的第一个参数转化为了UINT32的,然后调用CoffAddFixup()函数。
R_MIPS_26类型,是对应MIPS平台上跳转指令所使用的类型,后面relocation的地址,就是fix up指令的操作数的地址,切记不要修改操作码。这个类型的转化的算法和R_MIPS_32是一样的。
R_MIPS_LO16,R_MIPS_HI16,R_MIPS_HIGHER,R_MIPS_HIGHEST分别是修改64位地址中的低16位,高16为,次高和最高16位地址的情况。
这四种类型采用的算法都类似,参考32位地址的转化规则而来。以R_MIPS_HI16为例,其转化代码如下:
CoffAddFixup (
mCoffSectionsOffset[RelShdr->sh_info]
+ (Rel->r_offset - SecShdr->sh_addr),
EFI_IMAGE_REL_BASED_HIGHADJ);
Value = Sym->st_value + ((Elf64_Rela*)Rel)->r_addend;
BreakValue(Value, &Low, &High, &Higher, &Highest);
CoffAddFixupEntry(Low);
CoffAddFixupEntry(Higher);
CoffAddFixupEntry(Highest);
首先还是调用CoffAddFixup函数,将Entry中需要relocation的地址写入到PE32+中的地址上,但是第二个参数现在为
EFI_IMAGE_REL_BASED_HIGHADJ,然后获取Entry中需要relocation的 symbol value的值,注意我们这里使用了
/* Relocations that need an addend field. */
typedef struct {
__Elf64_Addr__r_offset;_/* Location to be relocated. */
__Elf64_Xword_r_info;___/* Relocation type and symbol index. */
__Elf64_Sxword__r_addend;_/* Addend. */
} Elf64_Rela;
字段中的r_addend字段来组合一个新的symbol value的值。也就是代码Value = Sym->st_value + ((Elf64_Rela*)Rel)->r_addend;的含义。然后使用才分函数,将这个64位地址分别才分成四个16bit的数,然后分别想除了high16bit的其他数据添加到要relocation的Entry中。也就是后面的代码的含义。其中BreakValue的函数实现如下:
#define INLINE inline
#define LOWMASK 0xFFFF
#define HIGHOFFSET 0x8000
#define HIGHEROFFSET 0x80008000
#define HIGHESTOFFSET 0x800080008000
STATIC INLINE
VOID
BreakValue (
UINT64 Value,
INT16 *Low,
INT16 *High,
INT16 *Higher,
UINT16 *Highest
)
{
*Low = (Value & LOWMASK);
*High = (((Value + (UINT64)HIGHOFFSET) >> 16) & LOWMASK);
*Higher = (((Value + (UINT64)HIGHEROFFSET) >> 32) & LOWMASK);
*Highest = (((Value + (UINT64)HIGHESTOFFSET) >> 48) & LOWMASK);
}
代码中为什么要添加带符号的偏移,这是PE32+官网上有说明的。
其他类型,R_MIPS_LO16,R_MIPS_HIGHER,R_MIPS_HIGHEST的算法都和上面的类似,注意这三种类型,对应的PE32+的格式分别为EFI_IMAGE_REL_BASED_LOWADJ,EFI_IMAGE_REL_BASED_HIGHERADJ,EFI_IMAGE_REL_BASED_HIGHESTADJ
其他的relocation type都是什么都不需要做的,直接break就好。
这三种类型是自己定义的,PE32+在MIPS平台上没有定义。
这是第一部分,需要在MIPS平台上需要添加的代码。这个部分代码是被函数ConvertElf()调用,那么ConvertElf又是在什么时候被调用的呢?是在GemFw.c中的main()函数中被调用的,这个函数就是UEFI的编译命令GenFw对应的源码,也就是说GenFw具体做的事,都在这部分代码中体现出来了。这里面不再详细的介绍这部分代码。
第二部分:是在编译阶段rebase的时候,需要重新对所有的image重新relocated
其调用rebase函数的代码也是在GenFw.c中的main函数中,代码如下:
if (NegativeAddr) {
//
// Set Base Address to a negative value.
//
NewBaseAddress = (UINT64) (0 - NewBaseAddress);
}
if (mOutImageType == FW_REBASE_IMAGE) {
Status = RebaseImage (mInImageName, FileBuffer, NewBaseAddress);
} else {
Status = SetAddressToSectionHeader (mInImageName, FileBuffer, NewBaseAddress);
}
上面的代码调用的RebaseImage函数,在这个函数内部调用了PeCoffLoaderRelocateImage()函数需要将image重新relocated,其代码如下:
ImageContext.DestinationAddress = NewPe32BaseAddress;
Status = PeCoffLoaderRelocateImage (&ImageContext);
if (EFI_ERROR (Status)) {
Error (NULL, 0, 3000, "Invalid", "RelocateImage() call failed on rebase of %s", FileName);
free ((VOID *) MemoryImagePointer);
return Status;
}
在PeCoffLoaderRelocateImage()函数中,在不同平台的实现也有不同,具体在MIPS平台的实现就是我们自己添加的。
其代码如下:
//
// Run this relocation record
//
while (Reloc < RelocEnd) {
Fixup = FixupBase + (*Reloc & 0xFFF);
switch ((*Reloc) >> 12) {
case EFI_IMAGE_REL_BASED_ABSOLUTE:
break;
case EFI_IMAGE_REL_BASED_HIGH:
F16 = (UINT16 *) Fixup;
*F16 = (UINT16) (*F16 + ((UINT16) ((UINT32) Adjust >> 16)));
if (FixupData != NULL) {
*(UINT16 *) FixupData = *F16;
FixupData = FixupData + sizeof (UINT16);
}
break;
case EFI_IMAGE_REL_BASED_LOW:
F16 = (UINT16 *) Fixup;
*F16 = (UINT16) (*F16 + (UINT16) Adjust);
if (FixupData != NULL) {
*(UINT16 *) FixupData = *F16;
FixupData = FixupData + sizeof (UINT16);
}
break;
case EFI_IMAGE_REL_BASED_HIGHLOW:
F32 = (UINT32 *) Fixup;
*F32 = *F32 + (UINT32) Adjust;
if (FixupData != NULL) {
FixupData = ALIGN_POINTER (FixupData, sizeof (UINT32));
*(UINT32 *) FixupData = *F32;
FixupData = FixupData + sizeof (UINT32);
}
break;
case EFI_IMAGE_REL_BASED_DIR64:
F64 = (UINT64 *) Fixup;
*F64 = *F64 + (UINT64) Adjust;
if (FixupData != NULL) {
{
*(UINT16 *) FixupData = *F16;
FixupData = FixupData + sizeof (UINT16);
}
break;
case EFI_IMAGE_REL_BASED_HIGHLOW:
F32 = (UINT32 *) Fixup;
*F32 = *F32 + (UINT32) Adjust;
if (FixupData != NULL) {
FixupData = ALIGN_POINTER (FixupData, sizeof (UINT32));
*(UINT32 *) FixupData = *F32;
FixupData = FixupData + sizeof (UINT32);
}
break;
case EFI_IMAGE_REL_BASED_DIR64:
F64 = (UINT64 *) Fixup;
*F64 = *F64 + (UINT64) Adjust;
if (FixupData != NULL) {
FixupData = ALIGN_POINTER (FixupData, sizeof (UINT64));
*(UINT64 *) FixupData = *F64;
FixupData = FixupData + sizeof (UINT64);
}
break;
#if 0
case EFI_IMAGE_REL_BASED_HIGHADJ:
//
// Return the same EFI_UNSUPPORTED return code as
// PeCoffLoaderRelocateImageEx() returns if it does not recognize
// the relocation type.
//
ImageContext->ImageError = IMAGE_ERROR_FAILED_RELOCATION;
return RETURN_UNSUPPORTED;
#endif
default:
switch (MachineType) {
case EFI_IMAGE_MACHINE_IA32:
Status = PeCoffLoaderRelocateIa32Image (Reloc, Fixup, &FixupData, Adjust);
break;
case EFI_IMAGE_MACHINE_ARMT:
Status = PeCoffLoaderRelocateArmImage (&Reloc, Fixup, &FixupData, Adjust);
break;
case EFI_IMAGE_MACHINE_IA64:
Status = PeCoffLoaderRelocateIpfImage (Reloc, Fixup, &FixupData, Adjust);
break;
case EFI_IMAGE_MACHINE_MIPS64EL:
Status = PeCoffLoaderRelocateMipsImage (&Reloc, Fixup, &FixupData, Adjust);
break;
default:
Status = RETURN_UNSUPPORTED;
break;
}
if (RETURN_ERROR (Status)) {
ImageContext->ImageError = IMAGE_ERROR_FAILED_RELOCATION;
return Status;
}
}
//
// Next relocation record
//
Reloc += 1;
}
上面的函数PeCoffLoaderRelocateMipsImage (&Reloc, Fixup, &FixupData, Adjust)就是MIPS平台添加的函数。这个函数位于
BaseTools/Source/C/Common下面的PeCoffLoaderEx.c中。具体的函数实现如下:
#define INLINE inline
#define LOWMASK 0xFFFF
#define HIGHOFFSET 0x8000
#define HIGHEROFFSET 0x80008000
#define HIGHESTOFFSET 0x800080008000
#define INSTRUCTIONCODEMASK 0x03FFFFFF
#define INSTRUCTIONDATAMASK 0xFC000000
/*
*Compose a 64bit Value
* */
STATIC INLINE
VOID
ComposeValue (
UINT64 *Value,
INT16 Low,
INT16 High,
INT16 Higher,
INT16 Highest
)
{
*Value =(((UINT64)Highest << 48 ) + ((INT64)Higher << 32) + ((INT64)High << 16) + ((INT64)Low));
}
/*
*Break a 64bit Value
* */
STATIC INLINE
VOID
BreakValue (
UINT64 Value,
INT16 *Low,
INT16 *High,
INT16 *Higher,
UINT16 *Highest
)
{
*Low = (Value & LOWMASK);
*High = (((Value + (UINT64)HIGHOFFSET) >> 16) & LOWMASK);
*Higher = (((Value + (UINT64)HIGHEROFFSET) >> 32) & LOWMASK);
*Highest = (((Value + (UINT64)HIGHESTOFFSET ) >> 48) & LOWMASK);
}
STATIC INLINE
VOID
RelocateMipsLowAdj (
IN UINT16 **Reloc,
IN OUT CHAR8 *Fixup,
IN OUT CHAR8 **FixupData,
IN UINT64 Adjust
)
{
UINT64 Value;
UINT16 Highest;
INT16 Low,High,Higher;
//
// Compose a 64bit Value Base on Fixup and Reloc addr,*Fixup is the Low 16 bit of Value
//
ComposeValue(&Value, *(INT16 *)Fixup, *(INT16 *)(*Reloc + 1), *(INT16 *)(*Reloc + 2), *(UINT16 *)(*Reloc + 3));
//
// Modify Value Base on Adjust
//
Value += Adjust;
//
// BreakValue to get Low High Higher and Highest of 16 bit data
//
BreakValue(Value, &Low, &High, &Higher, &Highest);
//
// Write fixup instruction address
//
*(UINT16*) Fixup = Low;
*(INT16 *)(*Reloc + 1) = High;
*(INT16 *)(*Reloc + 2) = Higher;
*(INT16 *)(*Reloc + 3) = Highest;
//
// Modify Reloc pointer
//
*Reloc = *Reloc + 3;
//
// Modify FixupData if *FixupData != NULL
//
if (*FixupData != NULL) {
*(UINT16 *) (*FixupData) = Low;
*FixupData = *FixupData + sizeof (UINT16);
}
}
STATIC INLINE
VOID
RelocateMipsHighAdj (
IN UINT16 **Reloc,
IN OUT CHAR8 *Fixup,
IN OUT CHAR8 **FixupData,
IN UINT64 Adjust
)
{
UINT64 Value;
UINT16 Highest;
INT16 Low,High,Higher;
//
// Compose a 64bit Value Base on Fixup and Reloc addr,*Fixup is the High 16 bit of Value
//
ComposeValue(&Value, *(INT16 *)(*Reloc + 1), *(INT16 *)Fixup, *(INT16 *)(*Reloc + 2), *(UINT16 *)(*Reloc + 3));
//
// Modify Value Base on Adjust
//
Value += Adjust;
//
// BreakValue to get Low High Higher and Highest of 16 bit data
//
BreakValue(Value, &Low, &High, &Higher, &Highest);
//
// Write fixup instruction address
//
*(UINT16*) Fixup = High;
*(INT16 *)(*Reloc + 1) = Low;
*(INT16 *)(*Reloc + 2) = Higher;
*(INT16 *)(*Reloc + 3) = Highest;
//
// Modify Reloc pointer
//
*Reloc = *Reloc + 3;
//
// Modify FixupData if *FixupData != NULL
//
if (*FixupData != NULL) {
*(UINT16 *) (*FixupData) = High;
*FixupData = *FixupData + sizeof (UINT16);
}
}
STATIC INLINE
VOID
RelocateMipsHigherAdj (
IN UINT16 **Reloc,
IN OUT CHAR8 *Fixup,
IN OUT CHAR8 **FixupData,
IN UINT64 Adjust
)
{
UINT64 Value;
UINT16 Highest;
INT16 Low,High,Higher;
//
// Compose a 64bit Value Base on Fixup and Reloc addr,*Fixup is the Higher 16 bit of Value
//
ComposeValue(&Value, *(INT16 *)(*Reloc + 1), *(INT16 *)(*Reloc + 2), *(INT16 *)Fixup, *(UINT16 *)(*Reloc + 3));
//
// Modify Value Base on Adjust
//
Value += Adjust;
//
// BreakValue to get Low High Higher and Highest of 16 bit data
//
BreakValue(Value, &Low, &High, &Higher, &Highest);
//
// Write fixup instruction address
//
*(UINT16*) Fixup = Higher;
*(INT16 *)(*Reloc + 1) = Low;
*(INT16 *)(*Reloc + 2) = High;
*(UINT16 *)(*Reloc + 3) = Highest;
//
// Modify Reloc pointer
//
*Reloc = *Reloc + 3;
//
// Modify FixupData if *FixupData != NULL
//
if (*FixupData != NULL) {
*(UINT16 *) (*FixupData) = Higher;
*FixupData = *FixupData + sizeof (UINT16);
}
}
STATIC INLINE
VOID
RelocateMipsHighestAdj (
IN UINT16 **Reloc,
IN OUT CHAR8 *Fixup,
IN OUT CHAR8 **FixupData,
IN UINT64 Adjust
)
{
UINT64 Value;
UINT16 Highest;
INT16 Low,High,Higher;
//
// Compose a 64bit Value Base on Fixup and Reloc addr,*Fixup is the Highest 16 bit of Value
//
ComposeValue(&Value, *(INT16 *)(*Reloc + 1), *(INT16 *)(*Reloc + 2), *(UINT16 *)(*Reloc + 3), *(INT16 *)Fixup );
//
// Modify Value Base on Adjust
//
Value += Adjust;
//
// BreakValue to get Low High Higher and Highest of 16 bit data
//
BreakValue(Value, &Low, &High, &Higher, &Highest);
//
// Write fixup instruction address
//
*(UINT16*) Fixup = Highest;
*(INT16 *)(*Reloc + 1) = Low;
*(INT16 *)(*Reloc + 2) = High;
*(INT16 *)(*Reloc + 3) = Higher;
//
// Modify Reloc pointer
//
*Reloc = *Reloc + 3;
//
// Modify FixupData if *FixupData != NULL
//
if (*FixupData != NULL) {
*(UINT16 *) (*FixupData) = Highest;
*FixupData = *FixupData + sizeof (UINT16);
}
}
STATIC INLINE
VOID
RelocateMipsJmpAdj (
IN UINT16 **Reloc,
IN OUT CHAR8 *Fixup,
IN OUT CHAR8 **FixupData,
IN UINT64 Adjust
)
{
UINT32 FixupVal;
UINT32 Instruction;
//
//read instruction
//
Instruction = *(UINT32*)Fixup;
//
//fixup instruction address base on Adjust,note keep instruction code not be changed
//
FixupVal = (Instruction & INSTRUCTIONCODEMASK) + ((Adjust>>2) & INSTRUCTIONCODEMASK);
FixupVal &= INSTRUCTIONCODEMASK;
Instruction = (Instruction & INSTRUCTIONDATAMASK) | (FixupVal);
//
//write fixup instruction
//
*(UINT32*)Fixup = Instruction;
//
//modify FixupData if *FixupData != NULL
//
if (*FixupData != NULL) {
*FixupData = ALIGN_POINTER (*FixupData, sizeof (UINT32));
*(UINT32 *) (*FixupData) = Instruction;
*FixupData = *FixupData + sizeof (UINT32);
}
}
/*********************************************************************************************************************
* Reloc is the pointer of every efi need relocated block address,every
* efi will have some block needed to relocated.
*
* Reloc = RelocBase + 8 (RelocBase is the addr of start of block)
*
* Fixup is the pointer of instruction address,this address needed fixup
* base on Adjust.such as before relocated address of Fixup is 0x98000000031xxxxx
* and after relocated address of Fixup is 0x980000000Exxxxxx.
*
* Fixup = ImageContext->ImageAddress + RelocBase->VirtualAddress + (*Reloc&0xfff) - TestrippedOffset
*
* FixupData is the pointer of instruction data,and will be modofied base on Fixup address and RelocType.
* RelocType = **Reloc>>12,which is the number of pagesize,the pagesize is 4K in mips64 platform.
*
* FixupData = ImageContext->FixupData (ImageContext set by function of PeCoffLoaderGetImageInfo())
*
* Adjust is the offset betwen the address of load efi address and the image of start address.
* such as eht efi image store at 0x9800000003xxxxxx and will be load at 0x980000000exxxxxx in run time,
* Adjust = 0x980000000exxxxxx - 0x9800000003xxxxxx.every block needed relocated which Adjust is equal in one efi image.
*
* Adjust = BaseAddress - Hdr.pe32Plus->OptionalHeader.ImageBase
*
* BaseAddress is the address of load efi image address.
* Hdr.pe32Plus->OptionalHeader.ImageBase is read from Pe32+ format.
*
************************************************* *******************************************************************/
RETURN_STATUS
PeCoffLoaderRelocateMipsImage (
IN UINT16 **Reloc,
IN OUT CHAR8 *Fixup,
IN OUT CHAR8 **FixupData,
IN UINT64 Adjust
)
{
UINT8 RelocType;
//
// Get RelocType,RelocType = Reloc/PAGE_SIZE
//
RelocType = ((**Reloc) >> 12);
switch (RelocType) {
//
// Fix up RelocType is 4 Condition
//
case EFI_IMAGE_REL_BASED_HIGHADJ:
RelocateMipsHighAdj(Reloc, Fixup, FixupData, Adjust);
break;
//
// Fix up RelocType is 5, instruction of j and jar,
//
case EFI_IMAGE_REL_BASED_MIPS_JMPADDR:
RelocateMipsJmpAdj(Reloc,Fixup,FixupData,Adjust);
break;
//
// Fix up RelocType is 6 Condition
//
case EFI_IMAGE_REL_BASED_LOWADJ:
RelocateMipsLowAdj(Reloc, Fixup, FixupData, Adjust);
break;
//
// Fix up RelocType is 7 Condition
//
case EFI_IMAGE_REL_BASED_HIGHERADJ:
RelocateMipsHigherAdj(Reloc, Fixup, FixupData, Adjust);
break;
//
// Fix up RelocType is 8 Condition
//
case EFI_IMAGE_REL_BASED_HIGHESTADJ:
RelocateMipsHighestAdj(Reloc, Fixup, FixupData, Adjust);
break;
default:
return RETURN_UNSUPPORTED;
}
return RETURN_SUCCESS;
}
上面的代码就是我们添加的,主要的relocation type其实就五中,其他的都是公用的,代码已经写好。
下面我们看一下主要的五种relocation type 对应的算法:
case EFI_IMAGE_REL_BASED_MIPS_JMPADDR:
RelocateMipsJmpAdj(Reloc,Fixup,FixupData,Adjust);
break;
这个对应的是修改MIPS平台的j指令和jar指令的,函数RelocateMipsJmpAdj()的参数,Reloc表示需要relocation的Entry的起始地址,Fixup是每一个image中对应的指令的地址,FixupData是指令地址下的数据。Adjust是每个image的start address基于load内存之后实际运行的地址偏移。其他四种类型中,参数一致。
下面看一下具体的代码实现:
STATIC INLINE
VOID
RelocateMipsJmpAdj (
IN UINT16 **Reloc,
IN OUT CHAR8 *Fixup,
IN OUT CHAR8 **FixupData,
IN UINT64 Adjust
)
{
UINT32 FixupVal;
UINT32 Instruction;
//
//read instruction
//
Instruction = *(UINT32*)Fixup;
//
//fixup instruction address base on Adjust,note keep instruction code not be changed
//
FixupVal = (Instruction & INSTRUCTIONCODEMASK) + ((Adjust>>2) & INSTRUCTIONCODEMASK);
FixupVal &= INSTRUCTIONCODEMASK;
Instruction = (Instruction & INSTRUCTIONDATAMASK) | (FixupVal);
//
//write fixup instruction
//
*(UINT32*)Fixup = Instruction;
//
//modify FixupData if *FixupData != NULL
//
if (*FixupData != NULL) {
*FixupData = ALIGN_POINTER (*FixupData, sizeof (UINT32));
*(UINT32 *) (*FixupData) = Instruction;
*FixupData = *FixupData + sizeof (UINT32);
}
}
首先读取需要fix up的地址指令,然后将这个地址与上0x03ffffff获取到32位指令中的后26位的指令操作数,将这个操作数的地址加上Adjust相关操作后的偏移作为一个新的指令操作数。然后将这个操作数在与上0x03ffffff防止前6位的指令操作码被修改,然后将这个新的操作数和原来的指令相或,得到新的跳转指令。然后将这个指令写回到需要fix up的地址上和地址对应的数据上。
其他四中的算法类似,都是采用32位地址的算法,将其应用到64位上。现在我们拿EFI_IMAGE_REL_BASED_HIGHADJ为例进行说明,其代码如下:
VOID
RelocateMipsHighAdj (
IN UINT16 **Reloc,
IN OUT CHAR8 *Fixup,
IN OUT CHAR8 **FixupData,
IN UINT64 Adjust
)
{
UINT64 Value;
UINT16 Highest;
INT16 Low,High,Higher;
//
// Compose a 64bit Value Base on Fixup and Reloc addr,*Fixup is the High 16 bit of Value
//
ComposeValue(&Value, *(INT16 *)(*Reloc + 1), *(INT16 *)Fixup, *(INT16 *)(*Reloc + 2), *(UINT16 *)(*Reloc + 3));
//
// Modify Value Base on Adjust
//
Value += Adjust;
//
// BreakValue to get Low High Higher and Highest of 16 bit data
//
BreakValue(Value, &Low, &High, &Higher, &Highest);
//
// Write fixup instruction address
//
*(UINT16*) Fixup = High;
*(INT16 *)(*Reloc + 1) = Low;
*(INT16 *)(*Reloc + 2) = Higher;
*(INT16 *)(*Reloc + 3) = Highest;
//
// Modify Reloc pointer
//
*Reloc = *Reloc + 3;
//
// Modify FixupData if *FixupData != NULL
//
if (*FixupData != NULL) {
*(UINT16 *) (*FixupData) = High;
*FixupData = *FixupData + sizeof (UINT16);
}
}
如上面的代码,首先获取到要需要fix up的64位的地址,地址的高16位是我们需要fix up 的字段,将这个地址加上要调整的偏移之后,在将这个地址才分成对应的4个16bit的数据,然后将其高16为的数据写入到fix up对应的地址段中,其余的依旧写回。然后再更新fixupdata的值。最后调整reloc指针的位置。
其他的EFI_IMAGE_REL_BASED_LOWADJ,EFI_IMAGE_REL_BASED_HIGHERADJ,EFI_IMAGE_REL_BASED_HIGHESTADJ和这个采用的算法是一样的,只不过分别代表的需要的fix up的字段不同,其代码不再详细介绍。
这是整个第二部分需要修改的代码。
第三部分:添加运行的时候需要relocate image的代码:
其代码位于MdePkg/Library/BasePeCoffLib/Mips/PeCoffLoaderEx.c中,这个文件是自己添加的,原来没有。这部分添加的代码和第二部分代码完全可以复用,只不过调用的位置不同,和函数的名字不同。这里不再列举代码。简单的说明其函数的调用时机和调用关系。
修改的函数名字为PeCoffLoaderRelocateImageEx(),其又被函数PeHotRelocateImageEx()分装了一下,其代码如下:
RETURN_STATUS
PeHotRelocateImageEx (
IN UINT16 **Reloc,
IN OUT CHAR8 *Fixup,
IN OUT CHAR8 **FixupData,
IN UINT64 Adjust
)
{
/* to check */
return PeCoffLoaderRelocateImageEx (Reloc, Fixup, FixupData, Adjust);
}
那么函数PeHotRelocateImageEx()在UEFI运行的时候哪些地方会调用这个函数呢?
其实有两个地方在调用,分别位于Pei阶段和Dxe阶段。下面详细的介绍下每个阶段的调用过程。
Pei:
这个阶段是直接调用的函数PeCoffLoaderRelocateImage(),这个函数是由函数LoadAndRelocatePeCoffImage()调用,其代码位于
MdeModulePkg/Core/Pei/Image/Image.c中。其调用关系依次如下:
PeCoffLoaderRelocateImage()<-----LoadAndRelocatePeCoffImage()<-----PeiLoadImageLoadImage()<-----PeiLoadImageLoadImageWrapper(),而PeiLoadImageLoadImageWrapper是通过mPeiLoadImagePpi注册到gPpiLoadFilePpiList中的,gPpiLoadFilePpiList又是InitializeImageServices函数在Pei阶段注册系统服务的时候注册进去的。真正调用的时候是在函数
PeiDispatcher ()----->PeiLoadImage()----->
PpiStatus = PeiServicesLocatePpi (
&gEfiPeiLoadFilePpiGuid,
Index,
NULL,
(VOID **)&LoadFile
);
if (!EFI_ERROR (PpiStatus)) {
Status = LoadFile->LoadFile (
LoadFile,
FileHandle,
&ImageAddress,
&ImageSize,
EntryPoint,
AuthenticationState
);
然后用gEfiPeiLoadFilePpiGuid来located出函数接口,这个接口LoadFile就是就是mPeiLoadImagePpi,而LoadFile->LoadFile 其实就是调用函数PeiLoadImageLoadImageWrapper,去load一个efi文件,在load的时候需要将其reloacted到内存中的某一个地址。然后找到entrypoint开始运行。在Pei前期,内存还没有初始化之前的模块是在cache中运行的,这些image也是需要relocated的。等到内存初始化之后,再次调用PeiCore.efi的时候,同样还是需要调用这个流程。到了DXE阶段后,就会调用Dxe阶段的relocated流程。
Dxe:
这个阶段是从函数DxeMain()----->CoreDispatcher()----->CoreLoadImage()----->CoreLoadImageCommon()----->CoreLoadPeImage()----->PeCoffLoaderRelocateImage()----->PeCoffLoaderRelocateImageEx()
整个调用过程如上,根据代码可知,在DXE阶段的每一个模块在load到内存之后都需要重新relocation,这个过程是每一个模块都必须有的,这样才能够运行。在调试的时候,确实也是如此,这部分添加的打印,在每个模块被加载到内存之后,都需要relocated的过程,之后才开始真正的运行。
这就是整个UEFI从编译到运行过程中涉及到relocation的地方。
希望对做uefi开发的人能够有帮助,同时哪里不对,希望指点,非常感谢!
标签:case,Relocation,Value,FixupData,UEFI,Reloc,MIPS,原理,Fixup 来源: https://blog.csdn.net/Lq19880521/article/details/83182870