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

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章