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开发的人能够有帮助,同时哪里不对,希望指点,非常感谢!

 

 

 

 

©️2020 CSDN 皮肤主题: 技术黑板 设计师:CSDN官方博客 返回首页