UEFI开发实战SlimBootloader中调用FSP

目录
  • 综述
  • 编译
    • PostBuild
    • Build
    • PostBuild
    • FSP二进制组成分析
  • 使用
    • Stage1B
    • Stage2

综述

FSP的全称是Firmware Support Package。FSP有以下的特性:

  • FSP提供了Intel重要组件(包括处理器、内存控制器、芯片组等)的初始化;
  • FSP被编译成独立的二进制,并可以集成到Bootloader中,这里说的Bootloader可以是Slim Bootloader,coreboot,UEFI等等;
  • FSP的优点有免费、方便集成、可减少开发时间,等等。

FSP中包含若干个部分,如下图所示:

按作用来分它包含三个大的组件,分别是:

FSP-T:它主要用来初始化CACHE以及其它早期需要的初始化,对应提供给外部的接口是TempRamInit()

FSP-M:它主要用来初始化内存以及其它需要的初始化,对应提供给外部的接口是FspMemoryInit()TempRamExit(),前者用于内存初始化,后者用于处理FSP-T中使用的CACHE内容;

FPS-S:它主要是CPU和芯片组的初始化,对应提供给外部的接口是FspSiliconInit()NotifyPhase(),其中后者又会在不同的阶段调用,包括PCIE扫描之后,ReadyToBoot时和EndOfBootServices的时候;

按功能来区分,每个组件都包含头部、配置和API三个部分。头部是固定的,配置用于一些可控的定制化,API就是功能代码。Bootloader要操作FSP,就需要完成文件配置,接口调用等。

BootLoader调用FSP的整个流程如下图所示:

Bootloader中需要有相应的代码做上述的操作,以Slim Bootloader为例,有一个IntelFsp2Pkg用来处理FSP相关的内容。不过需要注意,这里的IntelFsp2Pkg并不提供FSP源代码的,只是提供了EDK与FSP之间的中间层,真正的用来初始化Intel组件的FSP的代码并没有开源,所以这里也拿不到,不过可以拿到用于QEMU的FSP源代码,后续使用的就是这个。

编译

代码主要是https://gitee.com/jiangwei0512/edk2-beni中的QemuFspPkg,它是用在QEMU上的FSP,它有源码可以下载,而其它Intel的FSP基本是不开源的,没有办法下载到,所以这里只能用QEMU的FSP作为示例。

QEMU对应的FSP通过BuildFsp.py进行编译得到,该脚本执行三个步骤:

  • Prebuild
  • Build
  • PostBuild

PostBuild

Prebuild的流程如下:

构建FspHeader.inf实际上就是创建FSP Header的头部,它是固定的格式,位于QemuFspPkg\FspHeader\FspHeader.aslc,内容如下:

TABLES mTable =
{
  {
    FSP_INFO_HEADER_SIGNATURE,                    // UINT32  Signature  (FSPH)
    sizeof(FSP_INFO_HEADER),                      // UINT32  HeaderLength;
    {0x00, 0x00},                                 // UINT8   Reserved1[2];
    FixedPcdGet8(PcdFspHeaderSpecVersion),        // UINT8   SpecVersion;
    FixedPcdGet8(PcdFspHeaderRevision),           // UINT8   HeaderRevision;
    FixedPcdGet32(PcdFspImageRevision),           // UINT32  ImageRevision;

    UINT64_TO_BYTE_ARRAY(
    FixedPcdGet64(PcdFspImageIdString)),          // CHAR8   ImageId[8];

    0x12345678,                                   // UINT32  ImageSize;
    0x12345678,                                   // UINT32  ImageBase;

    FixedPcdGet16(PcdFspImageAttributes),         // UINT16  ImageAttribute;
    FixedPcdGet16(PcdFspComponentAttributes),     // UINT16  ComponentAttribute; Bits[15:12] - 0001b: FSP-T, 0010b: FSP-M, 0011b: FSP-S
    0x12345678,                                   // UINT32  CfgRegionOffset;
    0x12345678,                                   // UINT32  CfgRegionSize;
    0x00000000,                                   // UINT32  Reserved2;

    0x00000000,                                   // UINT32  TempRamInitEntry;
    0x00000000,                                   // UINT32  Reserved3;
    0x00000000,                                   // UINT32  NotifyPhaseEntry;
    0x00000000,                                   // UINT32  FspMemoryInitEntry;
    0x00000000,                                   // UINT32  TempRamExitEntry;
    0x00000000,                                   // UINT32  FspSiliconInitEntry;
  },
  {
    FSP_INFO_EXTENDED_HEADER_SIGNATURE,           // UINT32  Signature  (FSPE)
    sizeof(FSP_INFO_EXTENDED_HEADER),             // UINT32  Length;
    FSPE_HEADER_REVISION_1,                       // UINT8   Revision;
    0x00,                                         // UINT8   Reserved;
    {FSP_PRODUCER_ID},                            // CHAR8   FspProducerId[6];
    0x00000001,                                   // UINT32  FspProducerRevision;
    0x00000000,                                   // UINT32  FspProducerDataSize;
  },
  {
    FSP_FSPP_SIGNATURE,                           // UINT32  Signature  (FSPP)
    sizeof(FSP_PATCH_TABLE),                      // UINT16  Length;
    FSPP_HEADER_REVISION_1,                       // UINT8   Revision;
    0x00,                                         // UINT8   Reserved;
    1                                             // UINT32  PatchEntryNum;
  },
    0xFFFFFFFC                                    // UINT32  Patch FVBASE at end of FV
};

里面的某些数据在之后还会被修改,通过这个头部就可以找到对应API的位置,从而进行调用。

UPD txt文件是Build\QemuFspPkg\DEBUG_VS2019\FV(根据编译工具的不同,对应的目录可能存在差异)下的如下内容:

txt文件名对应的是三个FSP组件的GUID(位于BuildFsp.py):

    FspGuid = {
        'FspTUpdGuid'       : '34686CA3-34F9-4901-B82A-BA630F0714C6',
        'FspMUpdGuid'       : '39A250DB-E465-4DD1-A2AC-E2BD3C0E2385',
        'FspSUpdGuid'       : 'CAE3605B-5B34-4C85-B3D7-27D54273C40F'
    }

里面的内容主要是一些PCD,以39A250DB-E465-4DD1-A2AC-E2BD3C0E2385.txt为例:

## @file
#
#  THIS IS AUTO-GENERATED FILE BY BUILD TOOLS AND PLEASE DO NOT MAKE MODIFICATION.
#
#  This file lists all VPD informations for a platform collected by build.exe.
#
# Copyright (c) 2022, Intel Corporation. All rights reserved.<BR>
# This program and the accompanying materials
# are licensed and made available under the terms and conditions of the BSD License
# which accompanies this distribution.  The full text of the license may be found at
# http://opensource.org/licenses/bsd-license.php
#
# THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,
# WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.
#

gQemuFspPkgTokenSpaceGuid.Signature|DEFAULT|0x0000|8|0x4D5F4450554D4551
gQemuFspPkgTokenSpaceGuid.Revision|DEFAULT|0x0008|1|0x01
gQemuFspPkgTokenSpaceGuid.Reserved|DEFAULT|0x0009|23|{0x00}
gQemuFspPkgTokenSpaceGuid.Revision|DEFAULT|0x0020|1|0x01
gQemuFspPkgTokenSpaceGuid.Reserved|DEFAULT|0x0021|3|{0x00}
gQemuFspPkgTokenSpaceGuid.NvsBufferPtr|DEFAULT|0x0024|4|0x00000000
gQemuFspPkgTokenSpaceGuid.StackBase|DEFAULT|0x0028|4|0x00070000
gQemuFspPkgTokenSpaceGuid.StackSize|DEFAULT|0x002C|4|0x00010000
gQemuFspPkgTokenSpaceGuid.BootLoaderTolumSize|DEFAULT|0x0030|4|0x00000000
gPlatformFspPkgTokenSpaceGuid.Bootmode|DEFAULT|0x0034|4|0x00000000
gQemuFspPkgTokenSpaceGuid.Reserved1|DEFAULT|0x0038|8|{0x00}
gQemuFspPkgTokenSpaceGuid.SerialDebugPortAddress|DEFAULT|0x0040|4|0x00000000
gQemuFspPkgTokenSpaceGuid.SerialDebugPortType|DEFAULT|0x0044|1|0x02
gQemuFspPkgTokenSpaceGuid.SerialDebugPortDevice|DEFAULT|0x0045|1|0x02
gQemuFspPkgTokenSpaceGuid.SerialDebugPortStrideSize|DEFAULT|0x0046|1|0x02
gQemuFspPkgTokenSpaceGuid.UnusedUpdSpace0|DEFAULT|0x0047|0x0031|{0}
gQemuFspPkgTokenSpaceGuid.ReservedFspmUpd|DEFAULT|0x0078|4|{0x00}
gQemuFspPkgTokenSpaceGuid.UnusedUpdSpace1|DEFAULT|0x007C|0x0002|{0}
gQemuFspPkgTokenSpaceGuid.UpdTerminator|DEFAULT|0x007E|2|0x55AA

txt文件的来源是QemuFspPkg\QemuFspPkg.dsc,里面有这些PCD的初始化值,位于[PcdsDynamicVpd.Upd]这个Section,其中的UnusedUpdSpaceX也是在dsc文件中通过PCD指定的偏移来确定的。

3.UPD bin文件名也对应到前面提到的GUID,以39A250DB-E465-4DD1-A2AC-E2BD3C0E2385.bin为例:

bin文件跟txt文件中的PCD值是一一对应的。

4.UPD头文件和bsf文件比较直观,不做详细说明,它们也是通过QemuFspPkg\QemuFspPkg.dsc创建的。UPD头文件中还包含一个通用的头部结构体:

#pragma pack(1)
///
/// FSP_UPD_HEADER Configuration.
///
typedef struct {
  ///
  /// UPD Region Signature. This signature will be
  /// "XXXXXX_T" for FSP-T
  /// "XXXXXX_M" for FSP-M
  /// "XXXXXX_S" for FSP-S
  /// Where XXXXXX is an unique signature
  ///
  UINT64                      Signature;
  ///
  /// Revision of the Data structure.
  ///   For FSP spec 2.0/2.1 value is 1.
  ///   For FSP spec 2.2 value is 2.
  ///
  UINT8                       Revision;
  UINT8                       Reserved[23];
} FSP_UPD_HEADER;
#pragma pack()

每个平台的UPD和bsf内容都是不同的,甚至同一个平台的不同版本也可能存在差异,不同的FSP-X对应不同的UPD,分别是FSPT_UPDFSPM_UPDFSPS_UPD,它们分别存放在FsptUpd.h、FspmUpd.h和FspsUpd.h中。以FSPM_UPD结构体为例:

/** Fsp M UPD Configuration **/
typedef struct {

/** Offset 0x0000 **/
  FSP_UPD_HEADER              FspUpdHeader;

/** Offset 0x0020 **/
  FSPM_ARCH_UPD               FspmArchUpd;

/** Offset 0x0040 **/
  FSP_M_CONFIG                FspmConfig;

/** Offset 0x007C **/
  UINT8                       UnusedUpdSpace1[2];

/** Offset 0x007E **/
  UINT16                      UpdTerminator;
} FSPM_UPD;

其中的内容跟前面的UPD txt中的PCD一一对应。

上述的文件都在Build\QemuFspPkg\DEBUG_VS2019\FV(根据编译工具的不同,对应的目录可能存在差异)创建,总的来说就是为了创建FSP的UPD配置文件和对应用在代码中的头文件,头文件会被拷贝到其它位置也是为了代码能够调用到。而UPD配置文件会通过二进制的方式包含到QemuFspPkg\QemuFspPkg.fdf,下面是一个示例:

#
# Project specific configuration data files
#
!ifndef $(CFG_PREBUILD)
FILE RAW = $(FSP_M_UPD_FFS_GUID) {
    SECTION RAW = $(OUTPUT_DIRECTORY)/$(TARGET)_$(TOOL_CHAIN_TAG)/FV/$(FSP_M_UPD_TOOL_GUID).bin
}
!endif

Build

该过程仅仅是执行build操作而已,对应的代码:

def Build (target, toolchain):
    cmd = '%s -p QemuFspPkg/QemuFspPkg.dsc -a IA32 -b %s -t %s -y Report%s.log' % (
        'build' if os.name == 'posix' else 'build.bat', target, toolchain, target)
    ret = subprocess.call(cmd.split(' '))
    if ret:
        Fatal('Failed to do Build QEMU FSP!')

    print('End of Build...')

可以看到执行对象是QemuFspPkg.dsc。完成这一步之后会生成FSP-M.Fv、FSP-S.Fv、FSP-T.Fv和QEMUFSP.fd。

到这里FSP二进制已经生成,就是QEMUFSP.fd,但是它不能直接使用,还需要后续操作。

PostBuild

这一步主要是通过PatchFv.py来修改前文生成的QEMUFSP.fd,Patch前后:

这里具体Patch了哪部分内容,需要先了解FSP二进制的组成部分,可以参考FSP二进制组成分析。这里Patch的大部分都是FSP Header中的内容,对应的默认初始化内容就是前面提到的QemuFspPkg\FspHeader\FspHeader.aslc,其结构体如下:

///
/// FSP Information Header as described in FSP v2.0 Spec section 5.1.1.
///
typedef struct {
  ///
  /// Byte 0x00: Signature ('FSPH') for the FSP Information Header.
  ///
  UINT32  Signature;
  ///
  /// Byte 0x04: Length of the FSP Information Header.
  ///
  UINT32  HeaderLength;
  ///
  /// Byte 0x08: Reserved.
  ///
  UINT8   Reserved1[2];
  ///
  /// Byte 0x0A: Indicates compliance with a revision of this specification in the BCD format.
  ///
  UINT8   SpecVersion;
  ///
  /// Byte 0x0B: Revision of the FSP Information Header.
  ///
  UINT8   HeaderRevision;
  ///
  /// Byte 0x0C: Revision of the FSP binary.
  ///
  UINT32  ImageRevision;
  ///
  /// Byte 0x10: Signature string that will help match the FSP Binary to a supported HW configuration.
  ///
  CHAR8   ImageId[8];
  ///
  /// Byte 0x18: Size of the entire FSP binary.
  ///
  UINT32  ImageSize;
  ///
  /// Byte 0x1C: FSP binary preferred base address.
  ///
  UINT32  ImageBase;
  ///
  /// Byte 0x20: Attribute for the FSP binary.
  ///
  UINT16  ImageAttribute;
  ///
  /// Byte 0x22: Attributes of the FSP Component.
  ///
  UINT16  ComponentAttribute;
  ///
  /// Byte 0x24: Offset of the FSP configuration region.
  ///
  UINT32  CfgRegionOffset;
  ///
  /// Byte 0x28: Size of the FSP configuration region.
  ///
  UINT32  CfgRegionSize;
  ///
  /// Byte 0x2C: Reserved2.
  ///
  UINT32  Reserved2;
  ///
  /// Byte 0x30: The offset for the API to setup a temporary stack till the memory is initialized.
  ///
  UINT32  TempRamInitEntryOffset;
  ///
  /// Byte 0x34: Reserved3.
  ///
  UINT32  Reserved3;
  ///
  /// Byte 0x38: The offset for the API to inform the FSP about the different stages in the boot process.
  ///
  UINT32  NotifyPhaseEntryOffset;
  ///
  /// Byte 0x3C: The offset for the API to initialize the memory.
  ///
  UINT32  FspMemoryInitEntryOffset;
  ///
  /// Byte 0x40: The offset for the API to tear down temporary RAM.
  ///
  UINT32  TempRamExitEntryOffset;
  ///
  /// Byte 0x44: The offset for the API to initialize the CPU and chipset.
  ///
  UINT32  FspSiliconInitEntryOffset;
  ///
  /// Byte 0x48: Offset for the API for the optional Multi-Phase processor and chipset initialization.
  ///            This value is only valid if FSP HeaderRevision is >= 5.
  ///            If the value is set to 0x00000000, then this API is not available in this component.
  ///
  UINT32  FspMultiPhaseSiInitEntryOffset;
} FSP_INFO_HEADER;

其中的ImageSizeImageBaseImageAttributeComponentAttributeCfgRegionOffsetCfgRegionSizeTempRamInitEntryOffsetFspMemoryInitEntryOffsetTempRamExitEntryOffsetFspSiliconInitEntryOffsetNotifyPhaseEntryOffset等都需要修改。

因为每个FSP-X都有一个FSP_INFO_HEADER结构体,所以前提提到的XXXOffset会针对不同的FSP-X组件做对应的修改,比如FSP-T只需要TempRamInitEntryOffset

除了FSP Header的Patch,这里还有一个点被Patch了:

它们对应的是模块的入口(IntelFsp2Pkg\FspSecCore\Ia32\FspHelper.nasm):

global ASM_PFX(FspInfoHeaderRelativeOff)
ASM_PFX(FspInfoHeaderRelativeOff):
   DD    0x12345678               ; This value must be patched by the build script

从上面的代码也可以看到这部分是需要Patch的。

FSP二进制组成分析

二进制的组成如下:

FSP每个组件都是一个FV,所以都有一个FV Header(EFI_FIRMWARE_VOLUME_HEADER,位于MdePkg\Include\Pi\PiFirmwareVolume.h),大小是0x48个字节,之后是一个FV Extended Header(EFI_FIRMWARE_VOLUME_EXT_HEADER,位于MdePkg\Include\Pi\PiFirmwareVolume.h),之后才是FSP的内容,如下图所示:

FSP组件的第一个模块是FSP Header,对应二进制(Header的第一个成员是"FSPH",最后一个成员是0xFFFFFFFC)中:

这里可以看到里面有一些数据比较奇怪,都是0x12345678和0x00000000,这些都是占位符,并不是真正的有效数据,是通过FspHeader.aslc生成的,在后期这些数据会被Patch成有效的值。

FSP组件的第二个模块是UPD数据,它在Prebuild中生成,对应的数据(UPD数据的第一个成员是Signature(本例中是QEMUPD_T),最后一个成员是0x55AA):

再之后是通用的模块。以QEMU中的FSP对应的fdf文件为例:

#
# FSP header
#
INF  RuleOverride = FSPHEADER   $(FSP_PACKAGE)/FspHeader/FspHeader.inf

#
# Project specific configuration data files
#
!ifndef $(CFG_PREBUILD)
FILE RAW = $(FSP_T_UPD_FFS_GUID) {
    SECTION RAW = $(OUTPUT_DIRECTORY)/$(TARGET)_$(TOOL_CHAIN_TAG)/FV/$(FSP_T_UPD_TOOL_GUID).bin
}
!endif

INF RuleOverride = RELOC   IntelFsp2Pkg/FspSecCore/FspSecCoreT.inf

使用

用于Slim Bootloader的FSP需要放到前者指定的目录,对于QEMU来说对应的是Silicon\QemuSocPkg\FspBin,同时FSP对应的UPD头文件也需要放到指定目录。之后执行Slim Bootloader的各个阶段都会调用FSP的API接口,这里一一说明。

Stage1A

Stage1A阶段会执行FSP中的FspTempRamInit()接口,由于是执行阶段的早期,这里只有汇编部分的代码,具体的位置在BootloaderCorePkg\Stage1A\Ia32\SecEntry.nasm,对应代码:

global  ASM_PFX(_ModuleEntryPoint)
ASM_PFX(_ModuleEntryPoint):
        movd    mm0, eax

        ;
        ; Read time stamp
        ;
        rdtsc
        mov     esi, eax
        mov     edi, edx

        ;
        ; Early board hooks
        ;
        mov     esp, EarlyBoardInitRet
        jmp     ASM_PFX(EarlyBoardInit)

EarlyBoardInitRet:
        mov     esp, FspTempRamInitRet
        jmp     ASM_PFX(FspTempRamInit)

这里jmp到BootloaderCorePkg\Library\FspApiLib\Ia32\FspTempRamInit.nasm:

global  ASM_PFX(FspTempRamInit)
ASM_PFX(FspTempRamInit):
        ;
        ; This hook is called to initialize temporay RAM
        ; ESI, EDI need to be preserved
        ; ESP contains return address
        ; ECX, EDX return the temprary RAM start and end
        ;

        ;
        ; Get FSP-T base in EAX
        ;
        mov     ebp, esp
        mov     eax, dword [ASM_PFX(PcdGet32(PcdFSPTBase))]

        ;
        ; Find the fsp info header
        ; Jump to TempRamInit API
        ;
        add     eax, dword [eax + 094h + FSP_HEADER_TEMPRAMINIT_OFFSET]
        mov     esp, TempRamInitStack
        jmp     eax

TempRamInitDone:
        mov     esp, ebp
        jmp     esp

FSP中的FspTempRamInit()真正的入口是PcdGet32(PcdFSPTBase)+ 094h + FSP_HEADER_TEMPRAMINIT_OFFSETPcdFSPTBase的值是:

gPlatformModuleTokenSpaceGuid.PcdFSPTBase | $(FSP_T_BASE)

FSP_T_BASE表示的是FSP-T.bin的开始位置,094h在前面也已经介绍过,其前面的内容是FV Header,该地址开始是FSP Header,而FSP_HEADER_TEMPRAMINIT_OFFSET是FSP Header的偏移,该位置对应成员是TempRamInitEntryOffset,到这里就对应起来了,FspTempRamInit()即是该位置的值。

不过对于FSP_T_BASE的值,它是FSP-T放到系统内存中位置的地址,可以在BootloaderCorePkg\Platform.dsc中找到:

DEFINE FSP_T_BASE = 0xFFFF0000

这个值也跟SBL二进制产生关系:

Flash Map Information: +------------------------------------------------------------------------+ | FLASH MAP | | (RomSize = 0x00721000) | +------------------------------------------------------------------------+ | NAME | OFFSET (BASE) | SIZE | FLAGS | +----------+------------------------+------------+-----------------------+ +------------------------------------------------------------------------+ | TOP SWAP A | +------------------------------------------------------------------------+ | SG1A | 0x711000(0xFFFF0000) | 0x010000 | Uncompressed, TS_A | +------------------------------------------------------------------------+ | TOP SWAP B | +------------------------------------------------------------------------+ | SG1A | 0x701000(0xFFFE0000) | 0x010000 | Uncompressed, TS_B | +------------------------------------------------------------------------+ | REDUNDANT A | +------------------------------------------------------------------------+ | KEYH | 0x700000(0xFFFDF000) | 0x001000 | Uncompressed, R_A | | CNFG | 0x6ff000(0xFFFDE000) | 0x001000 | Uncompressed, R_A | | FWUP | 0x6e7000(0xFFFC6000) | 0x018000 | Compressed , R_A | | SG1B | 0x6b7000(0xFFF96000) | 0x030000 | Compressed , R_A | | SG02 | 0x69f000(0xFFF7E000) | 0x018000 | Compressed , R_A | | EMTY | 0x681000(0xFFF60000) | 0x01e000 | Uncompressed, R_A | +------------------------------------------------------------------------+ | REDUNDANT B | +------------------------------------------------------------------------+ | KEYH | 0x680000(0xFFF5F000) | 0x001000 | Uncompressed, R_B | | CNFG | 0x67f000(0xFFF5E000) | 0x001000 | Uncompressed, R_B | | FWUP | 0x667000(0xFFF46000) | 0x018000 | Compressed , R_B | | SG1B | 0x637000(0xFFF16000) | 0x030000 | Compressed , R_B | | SG02 | 0x61f000(0xFFEFE000) | 0x018000 | Compressed , R_B | | EMTY | 0x601000(0xFFEE0000) | 0x01e000 | Uncompressed, R_B | +------------------------------------------------------------------------+ | NON REDUNDANT | +------------------------------------------------------------------------+ | PTES | 0x600000(0xFFEDF000) | 0x001000 | Uncompressed, NR | | IPFW | 0x5f0000(0xFFECF000) | 0x010000 | Uncompressed, NR | | EPLD | 0x3e3000(0xFFCC2000) | 0x20d000 | Uncompressed, NR | | PYLD | 0x2e3000(0xFFBC2000) | 0x100000 | Compressed , NR | | VARS | 0x2e1000(0xFFBC0000) | 0x002000 | Uncompressed, NR | | EMTY | 0x001000(0xFF8E0000) | 0x2e0000 | Uncompressed, NR | +------------------------------------------------------------------------+ | NON VOLATILE | +------------------------------------------------------------------------+ | RSVD | 0x000000(0xFF8DF000) | 0x001000 | Uncompressed, NV | +----------+------------------------+------------+-----------------------+Flash Map Information:
        +------------------------------------------------------------------------+
        |                              FLASH  MAP                                |
        |                         (RomSize = 0x00721000)                         |
        +------------------------------------------------------------------------+
        |   NAME   |     OFFSET  (BASE)     |    SIZE    |         FLAGS         |
        +----------+------------------------+------------+-----------------------+
        +------------------------------------------------------------------------+
        |                               TOP SWAP A                               |
        +------------------------------------------------------------------------+
        |   SG1A   |  0x711000(0xFFFF0000)  |  0x010000  |  Uncompressed, TS_A   |
        +------------------------------------------------------------------------+
        |                               TOP SWAP B                               |
        +------------------------------------------------------------------------+
        |   SG1A   |  0x701000(0xFFFE0000)  |  0x010000  |  Uncompressed, TS_B   |
        +------------------------------------------------------------------------+
        |                              REDUNDANT A                               |
        +------------------------------------------------------------------------+
        |   KEYH   |  0x700000(0xFFFDF000)  |  0x001000  |  Uncompressed, R_A    |
        |   CNFG   |  0x6ff000(0xFFFDE000)  |  0x001000  |  Uncompressed, R_A    |
        |   FWUP   |  0x6e7000(0xFFFC6000)  |  0x018000  |  Compressed  , R_A    |
        |   SG1B   |  0x6b7000(0xFFF96000)  |  0x030000  |  Compressed  , R_A    |
        |   SG02   |  0x69f000(0xFFF7E000)  |  0x018000  |  Compressed  , R_A    |
        |   EMTY   |  0x681000(0xFFF60000)  |  0x01e000  |  Uncompressed, R_A    |
        +------------------------------------------------------------------------+
        |                              REDUNDANT B                               |
        +------------------------------------------------------------------------+
        |   KEYH   |  0x680000(0xFFF5F000)  |  0x001000  |  Uncompressed, R_B    |
        |   CNFG   |  0x67f000(0xFFF5E000)  |  0x001000  |  Uncompressed, R_B    |
        |   FWUP   |  0x667000(0xFFF46000)  |  0x018000  |  Compressed  , R_B    |
        |   SG1B   |  0x637000(0xFFF16000)  |  0x030000  |  Compressed  , R_B    |
        |   SG02   |  0x61f000(0xFFEFE000)  |  0x018000  |  Compressed  , R_B    |
        |   EMTY   |  0x601000(0xFFEE0000)  |  0x01e000  |  Uncompressed, R_B    |
        +------------------------------------------------------------------------+
        |                             NON REDUNDANT                              |
        +------------------------------------------------------------------------+
        |   PTES   |  0x600000(0xFFEDF000)  |  0x001000  |  Uncompressed,  NR    |
        |   IPFW   |  0x5f0000(0xFFECF000)  |  0x010000  |  Uncompressed,  NR    |
        |   EPLD   |  0x3e3000(0xFFCC2000)  |  0x20d000  |  Uncompressed,  NR    |
        |   PYLD   |  0x2e3000(0xFFBC2000)  |  0x100000  |  Compressed  ,  NR    |
        |   VARS   |  0x2e1000(0xFFBC0000)  |  0x002000  |  Uncompressed,  NR    |
        |   EMTY   |  0x001000(0xFF8E0000)  |  0x2e0000  |  Uncompressed,  NR    |
        +------------------------------------------------------------------------+
        |                              NON VOLATILE                              |
        +------------------------------------------------------------------------+
        |   RSVD   |  0x000000(0xFF8DF000)  |  0x001000  |  Uncompressed,  NV    |
        +----------+------------------------+------------+-----------------------+

由于SBL会放到4G以下的空间,而FSP-T.Fv放在了SG1A中,大小是0x10000,所以位置就是0xFFFF0000。通过下述命令能够更清楚的看出来:

F:\Gitee\sbl>BootloaderCorePkg\Tools\IfwiUtility.py view -i Outputs\qemu\SlimBootloader.bin
IFWI                     [O:0x00000000  L:0x00721000]
  BIOS                   [O:0x00000000  L:0x00721000]
    NVS                  [O:0x00000000  L:0x00001000]
      RSVD               [O:0x00000000  L:0x00001000]
    NRD                  [O:0x00001000  L:0x00600000]
      EMTY               [O:0x00001000  L:0x002E0000]
      VARS               [O:0x002E1000  L:0x00002000]
      PYLD               [O:0x002E3000  L:0x00100000]
      EPLD               [O:0x003E3000  L:0x0020D000]
      IPFW               [O:0x005F0000  L:0x00010000]
      PTES               [O:0x00600000  L:0x00001000]
    RD1                  [O:0x00601000  L:0x00080000]
      EMTY               [O:0x00601000  L:0x0001E000]
      SG02               [O:0x0061F000  L:0x00018000]
      SG1B               [O:0x00637000  L:0x00030000]
      FWUP               [O:0x00667000  L:0x00018000]
      CNFG               [O:0x0067F000  L:0x00001000]
      KEYH               [O:0x00680000  L:0x00001000]
    RD0                  [O:0x00681000  L:0x00080000]
      EMTY               [O:0x00681000  L:0x0001E000]
      SG02               [O:0x0069F000  L:0x00018000]
      SG1B               [O:0x006B7000  L:0x00030000]
      FWUP               [O:0x006E7000  L:0x00018000]
      CNFG               [O:0x006FF000  L:0x00001000]
      KEYH               [O:0x00700000  L:0x00001000]
    TS1                  [O:0x00701000  L:0x00010000]
      SG1A               [O:0x00701000  L:0x00010000]
    TS0                  [O:0x00711000  L:0x00010000]
      SG1A               [O:0x00711000  L:0x00010000] ---- 这里的最后就是0x100000000的位置

SBL二进制和FSP-T.bin的对应关系:

最终可以查看到PcdGet32(PcdFSPTBase)+ 094h + FSP_HEADER_TEMPRAMINIT_OFFSET处的值是0x473(位于0x7110C4),这也跟编译FSP时的Patch对应:

Patched offset 0x000370C4:[00000000] with value 0x00000473 # TempRamInit API

从上图可以看到该位置的值是EB 0B 90 90 90等等,可以确定这些就是代码了,但是它对应到的是哪个模块呢?其实可以从QemuFspPkg\QemuFspPkg.fdf中找到答案:

[FV.FSP-T]
BlockSize          = $(FLASH_BLOCK_SIZE)
FvAlignment        = 16
ERASE_POLARITY     = 1
MEMORY_MAPPED      = TRUE
STICKY_WRITE       = TRUE
LOCK_CAP           = TRUE
LOCK_STATUS        = TRUE
WRITE_DISABLED_CAP = TRUE
WRITE_ENABLED_CAP  = TRUE
WRITE_STATUS       = TRUE
WRITE_LOCK_CAP     = TRUE
WRITE_LOCK_STATUS  = TRUE
READ_DISABLED_CAP  = TRUE
READ_ENABLED_CAP   = TRUE
READ_STATUS        = TRUE
READ_LOCK_CAP      = TRUE
READ_LOCK_STATUS   = TRUE
FvNameGuid         = 52F1AFB6-78A6-448f-8274-F370549AC5D0

#
# FSP header
#
INF  RuleOverride = FSPHEADER   $(FSP_PACKAGE)/FspHeader/FspHeader.inf

#
# Project specific configuration data files
#
!ifndef $(CFG_PREBUILD)
FILE RAW = $(FSP_T_UPD_FFS_GUID) {
    SECTION RAW = $(OUTPUT_DIRECTORY)/$(TARGET)_$(TOOL_CHAIN_TAG)/FV/$(FSP_T_UPD_TOOL_GUID).bin
}
!endif

INF RuleOverride = RELOC   IntelFsp2Pkg/FspSecCore/FspSecCoreT.inf

根据前面的介绍,FspHeader.inf是FSP Header,$(FSP_T_UPD_TOOL_GUID).bin是UPD配置文件,那么FspSecCoreT.inf就应该是包含FspTempRamInit()函数代码的模块了。

这里直接找对应的模块,它被编译成一个二进制FspSecCoreT.efi,efi文件符合的是《Microsoft Portable Executable and Common Object File Format Specification》(后称Spec)规范。文档可以点击下载。在这个文档中描述了efi二进制头部的格式如下:

通过它可以找到真正的代码入口位置,不过也不需要一一计算,可以通过对应的map文件(这里就是Build\QemuFspPkg\DEBUG_VS2019\IA32\IntelFsp2Pkg\FspSecCore\FspSecCoreT\DEBUG\FspSecCoreT.map)找到需要的入口:

  Address         Publics by Value              Rva+Base       Lib:Object
 0001:000001fb       _TempRamInitApi            0000041b     FspSecCoreT:FspApiEntryT.obj

可以看到地址是0000041b,查看FspSecCoreT.efi二进制:

可以看到数据已经对应上了。再进一步分析这些数据的话,会发现EB实际上是一个跳转指令(注意之类是16位的代码,所以是近跳转),可以参考《64-ia-32-architectures-software-developer-instruction-set-reference-manual.pdf》中的“JMP—Jump”章节:

cb表示的是跳转偏移,这里的值是0xB,对应的代码在IntelFsp2Pkg\FspSecCore\Ia32\SaveRestoreSseNasm.inc:

%macro ENABLE_SSE   0
            ;
            ; Initialize floating point units
            ;
            jmp     NextAddress
align 4 	; 需要4字节对齐,所以后面补充了90,表示的是nop指令,总共3个字节
            ;
            ; Float control word initial value:
            ; all exceptions masked, double-precision, round-to-nearest
            ;
FpuControlWord       DW      027Fh	; 占据2个字节
            ;
            ; Multimedia-extensions control word:
            ; all exceptions masked, round-to-nearest, flush to zero for masked underflow
            ;
MmxControlWord       DD      01F80h	; 占据2个字节
SseError:
            ;
            ; Processor has to support SSE
            ;
            jmp     SseError	; 对应的机器码是EB FE,因为是循环执行,相当于跳转回去执行同一条命令,而该命令是2个字节,所以就是-2,等于0xFE
NextAddress:	; 理论上到这里只有9个字节,但是代码中跳转了0xB,也就是有11个字节,多出来的2个字节用0补上了,应该也是为了4字节对齐

到这里整个调用流程就完整了。

Stage1B

本阶段SBL会调用FSP中的FspMemoryInit(),对应的代码在BootloaderCorePkg\Stage1B\Stage1B.c:

  // Initialize memory
  HobList = NULL;
  DEBUG ((DEBUG_INIT, "Memory Init\n"));
  AddMeasurePoint (0x2020);
  Status = CallFspMemoryInit (PCD_GET32_WITH_ADJUST (PcdFSPMBase), &HobList);
  AddMeasurePoint (0x2030);

CallFspMemoryInit()执行的最重要的代码如下:

  FspMemoryInit = (FSP_MEMORY_INIT)(UINTN)(FspHeader->ImageBase + \
                                           FspHeader->FspMemoryInitEntryOffset);

  Status = FspMemoryInit (&FspmUpd, HobList);

从这里可以看到这就是一个跳转的动作,而跳转的位置就是FSP_INFO_HEADER中的成员FspMemoryInitEntryOffset,这个在前面已经说明过。

FSP中对应的模块主要有:

#
# It is important to keep the proper order for these PEIMs
# for this implementation
#
INF RuleOverride = RELOC   IntelFsp2Pkg/FspSecCore/FspSecCoreM.inf

INF MdeModulePkg/Core/Pei/PeiMain.inf
INF MdeModulePkg/Universal/PCD/Pei/Pcd.inf

#
# Project specific PEIMs
#
INF $(FSP_PACKAGE)/FspmInit/FspmInit.inf

FspSecCoreM.inf可以认为是一个伪SEC代码,主要的目的就是为了进入之后的PEI阶段,即PeiMain.inf,它跟UEFI中的PEI没有本质的区别,不过能够Dispatch的模块仅有后面的两个,Pcd.inf只是功能模块在这里并不重要,而FspmInit.inf就是内存初始化的主体。下面会简单介绍其中的主要模块。

FspSecCoreM.inf对应的入口:

;----------------------------------------------------------------------------
; FspMemoryInit API
;
; This FSP API is called after TempRamInit and initializes the memory.
;
;----------------------------------------------------------------------------
global ASM_PFX(FspMemoryInitApi)
ASM_PFX(FspMemoryInitApi):
  mov    eax,  3 ; FSP_API_INDEX.FspMemoryInitApiIndex
  jmp    ASM_PFX(FspApiCommon)

对应的调用路径:

调用路径中大部分是汇编,不过有几个是C函数,因为在Stage1A中已经可以使用C函数了。SecStartup()位于UefiCpuPkg\SecCore\SecMain.c,是SEC的C函数入口,之后转入执行PeiMain,这个PEI阶段Dispatch的模块主要是FspmInit.inf,它完成真正的内存初始化操作。

FspmInit.inf模块中完成内存初始化的代码这里不多做介绍,因为真正的Intel平台中的代码要复杂的多,这里只是虚拟机的内存初始化,本身的意义不大,不过需要注意的是其中的某些代码:

EFI_PEI_NOTIFY_DESCRIPTOR mMemoryDiscoveredNotifyList = {
  (EFI_PEI_PPI_DESCRIPTOR_NOTIFY_DISPATCH | EFI_PEI_PPI_DESCRIPTOR_TERMINATE_LIST),
  &gEfiPeiMemoryDiscoveredPpiGuid,
  MemoryDiscoveredPpiNotifyCallback
};

//
// Now that all of the pre-permanent memory activities have
// been taken care of, post a call-back for the permanent-memory
// resident services, such as HOB construction.
// PEI Core will switch stack after this PEIM exit.  After that the MTRR
// can be set.
//
Status = PeiServicesNotifyPpi (&mMemoryDiscoveredNotifyList);

这个操作在gEfiPeiMemoryDiscoveredPpiGuid被安装后被调用,而安装动作在PeiCore()中完成:

    //
    // Alert any listeners that there is permanent memory available
    //
    PERF_INMODULE_BEGIN ("DisMem");
    Status = PeiServicesInstallPpi (&mMemoryDiscoveredPpi);

gEfiPeiMemoryDiscoveredPpiGuid对应的回调函数有很多个,这里关注的是FspmInit.inf模块中的。原因是这里有两层的跳转,其中有如下的代码:

EFI_STATUS
EFIAPI
MemoryDiscoveredPpiNotifyCallback (
  IN EFI_PEI_SERVICES           **PeiServices,
  IN EFI_PEI_NOTIFY_DESCRIPTOR  *NotifyDescriptor,
  IN VOID                       *Ppi
  )
{
  //
  // Migrate FSP-M UPD data before destroying CAR
  //
  MigrateFspmUpdData ();

  //
  // Give control back after MemoryInitApi
  //
  FspMemoryInitDone (HobListPtr);

  if (GetFspApiCallingIndex() == TempRamExitApiIndex) {
    DEBUG ((DEBUG_INFO | DEBUG_INIT, "Memory Discovered Notify completed ...\n"));

    //
    // Give control back after TempRamExitApi
    //
    FspTempRamExitDone ();
  }
}

这里的FspMemoryInitDone()执行之后,CPU又会跳转到SBL代码中去执行,直到SBL中再次调用FSP中的TempRamExit()这个API,对应SBL中的代码:

Status = CallFspTempRamExit (PCD_GET32_WITH_ADJUST (PcdFSPMBase), NULL);

然后会再次开始执行FspMemoryInitDone()之后的代码,直到FspTempRamExitDone()退出。

Stage1B中调用的两个API到这里就都介绍完毕了。

Stage2

本阶段SBL会调用FSP中FspSiliconInit(),对应的代码在BootloaderCorePkg\Stage2\Stage2.c:

  DEBUG ((DEBUG_INIT, "Silicon Init\n"));
  AddMeasurePoint (0x3020);
  Status = CallFspSiliconInit ();
  AddMeasurePoint (0x3030);
  FspResetHandler (Status);
  ASSERT_EFI_ERROR (Status);

跟Stage1B中调用FSP中的API一样,这里也是一个跳转:

  FspSiliconInit = (FSP_SILICON_INIT)(UINTN)(FspHeader->ImageBase + \
                                             FspHeader->FspSiliconInitEntryOffset)
  Status = FspSiliconInit (FspsUpdptr);

对应的FSP-S的执行过程跟FSP-M差不多,也有一个伪SEC模块,对应的模块如下所示:

#
# It is important to keep the proper order for these PEIMs
# for this implementation
#
INF RuleOverride = RELOC   IntelFsp2Pkg/FspSecCore/FspSecCoreS.inf

INF MdeModulePkg/Core/DxeIplPeim/DxeIpl.inf
INF RuleOverride = PE32  $(FSP_PACKAGE)/FspsInit/FspsInit.inf
INF RuleOverride = PE32  $(FSP_PACKAGE)/QemuVideo/QemuVideo.inf
INF RuleOverride = PE32    IntelFsp2Pkg/FspNotifyPhase/FspNotifyPhasePeim.inf

不过实际上,当调用FspSiliconInit()之后,代码还是从Stage1B中的FSP退出的位置开始执行的,即QemuFspPkg\FspmInit\FspmInit.c中的FspTempRamExitDone ()之后开始执行代码,其对应的函数是ReportAndInstallNewFv (),也就是说,Stage2调用FSP-S之后,还是从PeiMain开始执行,当前述的函数安装了FV之后,就又开始Dispatch,完成上述模块的执行。

Stage2还是调用FSP中的NotifyPhase(),对应SBL中的代码:

EFI_STATUS
EFIAPI
CallFspNotifyPhase (
  FSP_INIT_PHASE  Phase
  )
{
  FSP_INFO_HEADER            *FspHeader;
  FSP_NOTIFY_PHASE            NotifyPhase;
  NOTIFY_PHASE_PARAMS         NotifyPhaseParams;
  EFI_STATUS                  Status;

  FspHeader = (FSP_INFO_HEADER *)(UINTN)(PcdGet32 (PcdFSPSBase) + FSP_INFO_HEADER_OFF);

  ASSERT (FspHeader->Signature == FSP_INFO_HEADER_SIGNATURE);
  ASSERT (FspHeader->ImageBase == PcdGet32 (PcdFSPSBase));

  if (FspHeader->NotifyPhaseEntryOffset == 0) {
    return EFI_UNSUPPORTED;
  }

  NotifyPhase = (FSP_NOTIFY_PHASE)(UINTN)(FspHeader->ImageBase +
                                          FspHeader->NotifyPhaseEntryOffset);

  NotifyPhaseParams.Phase = Phase;

  DEBUG ((DEBUG_INFO, "Call FspNotifyPhase(%02X) ... ", Phase));
  if (IS_X64) {
    Status = Execute32BitCode ((UINTN)NotifyPhase, (UINTN)&NotifyPhaseParams, (UINTN)0, FALSE);
    Status = (UINTN)LShiftU64 (Status & ((UINTN)MAX_INT32 + 1), 32) | (Status & MAX_INT32);
  } else {
    Status = NotifyPhase (&NotifyPhaseParams);
  }
  DEBUG ((DEBUG_INFO, "%r\n", Status));

  return Status;
}

可以看到也只是一个简单的跳转。这里的参数FSP_INIT_PHASE对应的值:

///
/// Enumeration of FSP_INIT_PHASE for NOTIFY_PHASE.
///
typedef enum {
  ///
  /// This stage is notified when the bootloader completes the
  /// PCI enumeration and the resource allocation for the
  /// PCI devices is complete.
  ///
  EnumInitPhaseAfterPciEnumeration = 0x20,
  ///
  /// This stage is notified just before the bootloader hand-off
  /// to the OS loader.
  ///
  EnumInitPhaseReadyToBoot         = 0x40,
  ///
  /// This stage is notified just before the firmware/Preboot
  /// environment transfers management of all system resources
  /// to the OS or next level execution environment.
  ///
  EnumInitPhaseEndOfFirmware       = 0xF0
} FSP_INIT_PHASE;

标明了调用NotifyPhase()的具体位置。

以上就是UEFI开发实战SlimBootloader中调用FSP的详细内容,更多关于UEFI开发SlimBootloader调用FSP的资料请关注我们其它相关文章!

(0)

相关推荐

  • UEFI开发实战用户交互界面使用说明UNI文件

    综述 UEFI用户交互界面的实现涉及到多种不同类型的文件,这里要讲的是UNI文件,它也是其中最简单的一种.本文主要参考自<edk-ii-uni-specification.pdf>(以下简称参考文档).它可以在EDK II Specifications · tianocore/tianocore.github.io Wiki · GitHub下载到.文本的代码示例来自EDK2017,由于版本更新等原因,示例中的代码可能跟实际GIT库中的代码有一定的差异. 作用 关于UNI文件的作用,在参考文档

  • UEFI开发实战用户交互界面使用说明VFR文件

    目录 1. 综述 2. 作用 3. 语法 3.1 注释 3.2 预定义 3.3 数据结构 3.4 Forms相关表达式总览 3.4.1 formset 3.4.2 formset list 3.5 Form Set List 3.5.1 变量定义 3.5.2 控制语句 3.5.3 Question语句 3.5.4 form语句 3.6 Forms表达式 3.6.1 vfrStatementImage 3.6.2 vfrStatementLocked 3.6.3 vfrStatementRules

  • UEFI开发基础HII代码示例

    目录 代码示例 模块 formset form subtitle oneof string numeric text checkbox goto label 代码示例 代码 https://gitee.com/jiangwei0512/edk2-beni 模块 BeniPkg\DynamicCommand\SetupDynamicCommand\SetupDynamicCommand.inf. 这里通过一个命令setup来打开图形界面.图形界面的form在Page.vfr中,还有若干的uni文件

  • UEFI开发基础汇编代码的使用

    UEFI中使用汇编代码 EDK代码中包含一部分汇编代码,目前支持的有.S..asm和.nasm格式的汇编(第一个是AT&T汇编,后两个是Intel汇编,只是使用的汇编样式稍有不同,.nasm是开源和免费的,更加的通用),如果是在Windows下编译,一般使用的是NASM编译器,所以会使用.nasm格式的汇编文件,而编译工具也是免费的,可以在https://www.nasm.us/下载,并放到C:\Nasm目录,之后就可以在EDK代码中使用. 下面是一个示例,它是一个库模块: 首先创建inf文件:

  • UEFI开发实战用户交互界面基础说明

    目录 前言 启动 UiApp模块 字体 字符串 UI Entry 前言 本文以vUDK2017: https://github.com/tianocore/edk2.git Tag vUDK2017.中的代码为例说明UEFI用户交互界面的实现. 这里UEFI用户交互界面的实现载体是OVMF(使用QEMU启动),其形式如下: 它一般被叫做Front Page(后面将以该名称来称呼上述的界面),其下还包括Setup,Boot Manager,Device Manager等选项. 相比Legacy B

  • UEFI开发实战SlimBootloader中调用FSP

    目录 综述 编译 PostBuild Build PostBuild FSP二进制组成分析 使用 Stage1B Stage2 综述 FSP的全称是Firmware Support Package.FSP有以下的特性: FSP提供了Intel重要组件(包括处理器.内存控制器.芯片组等)的初始化: FSP被编译成独立的二进制,并可以集成到Bootloader中,这里说的Bootloader可以是Slim Bootloader,coreboot,UEFI等等: FSP的优点有免费.方便集成.可减少开

  • Android开发中调用系统相册上传图片到服务器OPPO等部分手机上出现短暂的显示桌面问题的解决方法

    要原因是主体样式设置的问题:这里把appTheme设置一个style即可: <item name="android:windowBackground">@color/white</item> <!--下面这个属性很重要,有时候会出现某些机型在调用系统相册的时候,短暂的出现手机桌面现象--> <item name="android:windowIsTranslucent">false</item> <i

  • 详解vue开发中调用微信jssdk的问题

    起因 微信分享网址时无法分享图片,这个问题需要用jssdk去解决.其实开始的时候时可以看到图片的,但后来微信禁止了.所以只能使用jssdk去解决. 普通网页开发很简单,但是使用vue或其他前端框架开发spa单页面webapp的时候就会有问题了.只要url发生变化就会报签名错误.其实微信官方上已经写了说明. 所有需要使用JS-SDK的页面必须先注入配置信息,否则将无法调用(同一个url仅需调用一次,对于变化url的SPA的web app可在每次url变化时进行调用,目前Android微信客户端不支

  • 微信小程序开发实战教程之手势解锁

    代码:https://github.com/jsongo/wx-gesture-lock 这个手势解锁的demo使用了https://github.com/lvming6816077/H5lock这个项目的算法和主逻辑,整合到微信小程序来,修改了很多地方的语法来适配小程序,去掉了window.document等函数,同时也添加了新的机制来解耦界面的操作和第三方库,这个下面会介绍到. 不过可惜的是,这个demo也只能在开发工具上玩玩,到真机上测试的时候,手指一滑动,页面会跟着滚动,手势没法使用.

  • JavaScript使用DeviceOne开发实战(四)仿优酷视频应用

    大家没有进行开发之前首先需要考虑系统的差异性,比如说IOS手机有没有回退键,所以在开发时一定要考虑二级解密需要有回退键,否则ios的手机就会陷入到这个页面回不去. 安卓系统有回退键,针对这个情况需要要求用户在3秒钟之内连续按回退键两次才退出系统,以此防止用户误按回退键,具体代码实现如下: [mw_shl_code=javascript,true]page.on("back", function(){ if (canBack) { global.exit(); } else { nf.t

  • 微信公众平台开发实战Java版之微信获取用户基本信息

    在关注者与公众号产生消息交互后,公众号可获得关注者的OpenID(加密后的微信号,每个用户对每个公众号的OpenID是唯一的.对于不同公众号,同一用户的openid不同). 公众号可通过本接口来根据OpenID获取用户基本信息,包括昵称.头像.性别.所在城市.语言和关注时间. 开发者可通过OpenID来获取用户基本信息.请使用https协议. 我们可以看看官方的文档:获取用户的基本信息. 接口调用请求说明 http请求方式: GET https://api.weixin.qq.com/cgi-b

  • C#实现微信跳一跳小游戏的自动跳跃助手开发实战

    一.前言: 前段时间微信更新了新版本后,带来的一款H5小游戏"跳一跳"在各朋友圈里又火了起来,类似以前的"打飞机"游戏,这游戏玩法简单,但加上了积分排名功能后,却成了"装逼"的地方,于是很多人花钱花时间的刷积分抢排名.后来越来越多的聪明的"程序哥们"弄出了不同方式不同花样的跳一跳助手(外挂?),有用JS实现的.有JAVA实现的.有Python实现的,有直接物理模式的.有机械化的.有量尺子的等等,简直是百花齐放啊-- 赶一下潮流

随机推荐