本文主要介紹的是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開發的人能夠有幫助,同時哪裏不對,希望指點,非常感謝!