Linux 下调用UEFI的函数

  • 摘要
  • Linux 调用UEFI function 时候调用约定的转换
  • 64bits calling convention
    • Microsoft calling convention
    • GCC x64 calling convention
  • Linux下UEFI服务调用

摘要

Linux 启动初期需要调用UEFI提供BootServices和调用exit_boot_services 之后UEFI runtime services。目前linux 内核也都把CONFIG_EFI_MIXED开关打开了,目的是支持64bit kernel 调用32bits UEFI services. 这篇博客主要介绍64bits linux 开机初期调用UEFI boot services 相关调用约定的转换代码。至于kernel 调用uefi runtime services 涉及到地址转换,页表的变化,workqueue,lock,等问题会在后面博客介绍。

Linux 调用UEFI function 时候调用约定的转换

在Linux 下如果要调用UEFI 提供的函数相当于调用一个ABI,那么就会涉及到参数如何传递的问题。UEFI 默认用的MS 64bit ABI 调用约定,Linux 调用函数的时候是用的GCC 调用约定,所以在Linux 下调用UEFI 函数,需要一个转换函数,把linux calling convention 转换成UEFI calling convention

calling convention 相关代码参考 arch/x86/platform/efi/efi_stub_64.S efi_call
64bits kernel 调用32bits UEFI services 相关代码参考efi_thunk_64.S

64bits calling convention

这节主要内容翻译与Microsoft Doc 文档,原文链接如下

https://docs.microsoft.com/en-us/cpp/build/x64-calling-convention?view=msvc-160
https://docs.microsoft.com/en-us/cpp/cpp/fastcall?view=msvc-160

因为UEFI也是用的Microsoft default Calling convention,所以为了兼容不同编译器,我们可以看到UEFI function 都会有一个EFIAPI 这个标识符,这个EFIAPI 在GCC 编译器下定义为 -DEFIAPI = __attribute__((ms_abi)) ,目的就是让GCC按照Microsoft 默认的ABI 调用约定。

Microsoft calling convention

Microsoft x64 Application Binary Interface(ABI) 默认用了一个four-register fast-call 调用约定,并且调用者在栈上申请内存为了让被调用者去存放这些寄存器(这个申请的栈空间也叫shadow store)这些后面会有代码演示,这种调用关系有一个限制,这个限制就是参数的大小要和寄存器大小匹配,比如寄存器大小有1,2,4, 8 bytes,如果参数大小超过个字节,那么要用一个指针去引用这个参数,一个参数不能跨越多个寄存器。所有的浮点型参数都是用的16bits XMM 寄存器。整数参数用RCX,RDX,R8,and R9。浮点型参数用XMM0L,XMM1L,XMM2L,and XMM3L. 参数大于4个时候从右往左依次压栈。

参数类型 第五个或者更高 第四个 第三个 第二个 第一个
浮点型 XMM0L XMM1L XMM2L XMM3L
整形 RCX RDX R8 R9

参数传递例子1-全是整形的参数

func1(int a, int b, int c, int d, int e, int f)
// a in RCX, b in RDX, c in R8, d in R9
// push f
// push e

参数传递例子2-全是浮点的参数

func2(float a, float b, float c, float d, float e, float f)
// a in XMM0, b in XMM1, c in XMM2, d in XMM3
// push f
// push e

参数传递例子3-整型,浮点型参数

func3(int a, double b, int c, float d, int e, float f)
// a in RCX, b in XMM1, c in R8, d in XMM3
// push f
// push e

GCC x64 calling convention

和Microsoft 类似但是有一点点区别,GCC用的是six-register 调用约定,对于大于6个的的参数也是从右往左压栈,对于前四个整形参数依次从左到右的顺序是rdi,rsi,rdx,rcx,r8,r9 ;浮点型也是用的XMM 寄存器,差别暂且略过。

刚刚有提到shadow store那么可以通过一个例子看看什么是shadow store,还有函数对于压栈的参数如何获取,这里已GCC 为例

//test.c
func1(int a, int b, int c, int d, int e, int f, int g)
{a++;g++;
}
int main()
{func1(1,2,3,4,5,6,7);
}
//gcc test.c -o test
// objdump -d test0000000000401102 <func1>:401102:       55                      push   %rbp401103:       48 89 e5                mov    %rsp,%rbp401106:       89 7d fc                mov    %edi,-0x4(%rbp)401109:       89 75 f8                mov    %esi,-0x8(%rbp)40110c:       89 55 f4                mov    %edx,-0xc(%rbp)40110f:       89 4d f0                mov    %ecx,-0x10(%rbp)401112:       44 89 45 ec             mov    %r8d,-0x14(%rbp)401116:       44 89 4d e8             mov    %r9d,-0x18(%rbp)40111a:       83 45 fc 01             addl   $0x1,-0x4(%rbp)40111e:       83 45 10 01             addl   $0x1,0x10(%rbp)401122:       90                      nop401123:       5d                      pop    %rbp401124:       c3                      retq   0000000000401125 <main>:401125:       55                      push   %rbp401126:       48 89 e5                mov    %rsp,%rbp401129:       6a 07                   pushq  $0x740112b:       41 b9 06 00 00 00       mov    $0x6,%r9d401131:       41 b8 05 00 00 00       mov    $0x5,%r8d401137:       b9 04 00 00 00          mov    $0x4,%ecx40113c:       ba 03 00 00 00          mov    $0x3,%edx401141:       be 02 00 00 00          mov    $0x2,%esi401146:       bf 01 00 00 00          mov    $0x1,%edi40114b:       e8 b2 ff ff ff          callq  401102 <f>

上图401129–401146 是参数传递,然后调用func1, 再看401106-401116 这几行,就是把这些传递参数的寄存器保存在栈中供func1去使用。
a++ a的值是放在%edi 中值为1 ,%edi 又存放在shadowbuf 里-0x4(%rbp)这个位置中
a++ 对应的汇编就是 addl $1, -4(%rbp)
g++ 对应的是汇编是 addl $1, 0x10(%rbp) 其中0x10(%rbp) 位置里面的值是7
看看目前堆栈图就容易理解

Linux下UEFI服务调用

首先以/x86/boot/compressed/eboot.c/setup_efi_pci()中查找所有的PciIoProtocol为例

//UEFI 环境下
Status = gBS->LocateHandle (ByProtocol,&gEfiPciIoProtocolGuid,NULL,&BufferSize,Handle);
//Linux 环境下
efi_guid_t pci_proto = EFI_PCI_IO_PROTOCOL_GUID;
status = efi_call_early(locate_handle,EFI_LOCATE_BY_PROTOCOL,&pci_proto, NULL, &size, pci_handle);

对比两个环境下调用BootServices的服务参数传递的差不多,linux 是用了efi_call_early 进行了封装,进一步对efi_call_early 进行分析下

efi_call_early(locate_handle,EFI_LOCATE_BY_PROTOCOL,&pci_proto, NULL, &size, pci_handle);
宏扩展之后如下:
__efi_early()->call((efi_is_64bit() ?                        \((efi_boot_services_64_t *)(unsigned long)__efi_early()->boot_services)->locate_handle : \((efi_boot_services_32_t *)(unsigned long)__efi_early()->boot_services)->locate_handle), 2,&pci_proto, ((void *)0), &size, pci_handle)__efi_early() 是一个function 返回的是efi_early结构体变量,再次替换后
efi_early->call(efi_early->boot_services->locate_handle, 2, &pci_proto,NULL, &size,pci_handle)// efi_early 这个结构体定义在x86/boot/compressed/head_64.S
.global efi64_config
efi64_config:.fill  5,8,0.quad  efi_call.byte   1
//efi_call 定义在x86/platform/efi/efi_stub_64.S
ENTRY(efi_call)pushq %rbpmovq %rsp, %rbpSAVE_XMMmov 16(%rbp), %raxsubq $48, %rspmov %r9, 32(%rsp)mov %rax, 40(%rsp)mov %r8, %r9mov %rcx, %r8mov %rsi, %rcxcall *%rdiaddq $48, %rspRESTORE_XMMpopq %rbpret
ENDPROC(efi_call)

efi_call 主要做的就是把GCC calling convention 转换成 Microsoft ABI calling convention, 但是其中有一个点要注意如下,.quad efi_call 在编译连接的时候地址已经固定了。但是我们kernel 是可以被UEFI 加载地址不是固定的,所以要对这个地址进行重定位,重定位代码如下

.global efi64_config
efi64_config:.fill  5,8,0.quad  efi_call.byte   1// 重定位efi_call 地址call  1f
1:  popq    %rbpsubq    $1b, %rbp/** Relocate efi_config->call().*/addq  %rbp, efi64_config+40(%rip)

call 1f 的时候,call 指令会帮我们把call 1f 下条指令地址压栈,也就是1 这个位置的加载地址,然后popq %rbp,就是把1 这个label 加载地址放入了rbp 中,subq $1b, %rbp 就是加载地址减去编译地址,我们就会得到一个差值。addq %rbp, efi64_config+40(%rip) 就是把efi_call 这个链接地址给修正到加载地址。

Linux 下调用UEFI的函数相关推荐

  1. Linux下的内存对齐函数

    在Linux下内存对齐的函数包括posix_memalign, aligned_alloc, memalign, valloc, pvalloc,其各个函数的声明如下: int posix_memal ...

  2. Linux系统常用函数,浅谈linux下的一些常用函数的总结(必看篇)

    1.exit()函数 exit(int n)  其实就是直接退出程序, 因为默认的标准程序入口为int main(int argc, char** argv),返回值是int型的. 一般在shell下 ...

  3. java调用c 生成so,Java在linux下调用C/C++生成的so文件

    Java在linux下调用C/C++生成的so文件 1. CplusUtil.java是java web工程中的一个工具类 内容如下: CplusUtil.java package cn.undone ...

  4. linux uname内核,Linux下confstr与uname函数_获取C库与内核信息

    Linux下confstr与uname函数_获取C库与内核信息 #include #include  //uname int main(int argc, char **argv[]) { struc ...

  5. Linux系统怎么编译sin,linux下gcc编译sin函数出错的问题

    linux下gcc编译sin函数出错的问题 收藏 Q: I keep getting errors due to library functions being undefined, but I'm ...

  6. linux下C语言main函数参数解析

    1. linux下常见的main函数有两种形式: int main(int argc, char **argv) int main(int argc, char * argv[]) 2. 参数介绍 第 ...

  7. 在Windows/Linux下调用API函数实现重启系统

    一.Linux下重启系统 linux下很简单,直接看代码: #include <unistd.h> #include <sys/reboot.h>bool rebootSyst ...

  8. linux下的常用时间函数总结

    1.Unix系统一直使用两种不同的时间值:"日历时间"和"进程时间" 1.1.日历时间,日历时间是从国际标准时间公元1970年1月1日00:00:00到现在所经 ...

  9. Linux下调用fork或system启动子进程的信号和资源释放相关问题

    最近一段时间,公司的网管系统二期优化需要新增功能,实现对网管客户端程序进行保护的监控脚本的自动更新及保护进程的监控告警.网管客户端程序分为两部分:客户端GatherClient及保护进程gatherc ...

最新文章

  1. mysql中char与varchar的区别分析(补充一句,int和integer没区别)
  2. php lalaogu cn,php安装编译时错误合集
  3. 成功解决WARNING: You do not appear to have an NVIDIA GPU supported by the 430.34 NVIDIA Linux graph
  4. OpenCV HoG描述符的实例(附完整代码)
  5. LeetCode 208. 实现 Trie (前缀树) —— 提供一套前缀树模板
  6. java math rint_Java Math.rint() 方法
  7. matlab gradient
  8. python 文件和目录基本操作_Python常用的文件及文件路径、目录操作方法汇总介绍...
  9. Node如何自动重启进程
  10. asp.net基础 笔试题(全解完整答案)
  11. vs调试时查看指针指向的内存区域的内容
  12. 滑模控制学习笔记(二)
  13. 什么是华为认证?华为技术认证工程师可以做什么?
  14. 易基因|干货:m6A RNA甲基化MeRIP-seq测序分析实验全流程解析
  15. 统一社会信用代码正则校验
  16. 简单实现Android图片三级缓存机制
  17. 技嘉1080显卡体质测试软件,技嘉AORUS GTX 1080 Gaming Box
  18. 关于小米文件管理器的介绍及源码下载
  19. Java中的类和对象
  20. 四、【python计算机视觉编程】照相机模型与增强现实

热门文章

  1. 先学会记笔记——MarkDown学习
  2. 第三代搜索引擎技术与P2P
  3. Python学习笔记之schedule 定时任务
  4. linux更改文件权限chown,Linux 文件权限 chmod chown
  5. linux cal命令使用,cal命令_Linux cal命令使用详解:显示当前日历或指定日期的日历...
  6. 操作系统:生产者消费者问题
  7. nginx配置文件解析加讲解
  8. service control manager 错误解决
  9. 又到了人来人往的日子
  10. 2020年电赛省赛题目A——无线运动传感器节点设计