/Main-stream-Architectures-OSes-Compilers-ABI-and-function-calling-conventions

各主流处理器与系统平台的ABI和函数调用约定,外加系统平台预定义的宏

GNU Affero General Public License v3.0AGPL-3.0

Main-stream Platform ABIs and function-calling conventions on most of Processor Architectures and Operating Systems

各主流处理器与系统平台的ABI和函数调用约定,外加系统平台预定义的宏


汇编语言相关


各大平台的ABI与函数调用约定


各大平台的预定义宏


在Apple环境下针对Apple A处理器架构的一些额外的预定义宏

首先,在所有Apple系统环境下一定有预定义宏——__APPLE__

  1. 列出指定架构的所有预定义宏:clang -arch <架构名> -E -dM - < /dev/null。其中 <架构名> 目前支持:armv5armv6armv7armv7sarm64。除此之外,当前macOS平台上Xcode自带的Apple LLVM编译器还支持这些架构:i386x86_64ppcppc64
  2. Apple平台下,除了支持__ARM_ARCH_'V'__这个标准的架构预定义宏之外,还支持__ARM_ARCH这个宏,这个宏后面跟着一个整数,指明当前ARM架构的大版本。比如,5表示ARMv5,8则表示ARM64。
  3. Apple平台下,如果当前Apple A处理器是arm64架构的话,那么它不仅支持__aarch64__这个标准的宏,而且还支持__arm64__这个宏。
  4. armv7s的版本预定义宏为:__ARM_ARCH_7S__
  5. arm64e架构的预定义宏为:__arm64e__。arm64e默认是不被构建的,当前Xcode 10的默认标准架构只有armv7arm64这两种。如果要额外添加的话需要在编译构建选项列表中的Architectures一栏中追加arm64e才行。

在Android系统环境下指定处理器架构

首先,在Android环境下一定有预定义宏——__ANDROID__

由于ARMv8-A架构有多个变种,比如ARMv8、ARMv8.1、ARMv8.2、ARMv8.3、ARMv8.4等等。为了能在NDK编译环境中指定架构名,我们可以用 -march=命令行选项。 比如:-march=armv8.2-a指定使用ARMv8.2-A架构。

在基于ARM64的Linux下要使用半精度浮点相关的指令集,需要在汇编语言中指定ARMv8.2-A架构外加FP16,如下所示:

set(CMAKE_ASM_FLAGS "-march=armv8.2-a+fp16 ${CMAKE_ASM_FLAGS}")

MSVC下对各个处理器平台的宏定义

  • x86: _M_IX86
  • x86_64: _M_X64
  • ARM: _M_ARM
  • ARM64: _M_ARM64

各编译器预定义的项目工程相关的预定义宏

  • Visual Studio:$(LocalDebuggerWorkingDirectory)表示当前工程中用于存放源文件的主目录。$(WindowsSDK_IncludePath)表示Windows Kit目录下的include目录下的um目录。$(WindowsSDK_LibraryPath_x64)表示Windows Kit目录下的Lib目录下的x64目录。查看更多的宏名可在Visual Studio中选择C/C++一栏下的“常规”,然后点击“附加包含目录”,再点击“编辑”即可。
  • Xcode:$(SRCROOT)表示当前项目工程的根目录。$(PROJECT_DIR)所表示的工程路径与$(SRCROOT)一样。$(PROJECT_NAME)表示当前项目名称。
  • cmake-variables(7)
  • CMake:${PROJECT_SOURCE_DIR}表示当前工程项目中用于存放源文件的主目录。在Android Studio中则是当前项目工程根目录下的 app/src/main/cpp/ 目录。在Android Studio的cmake中,${ANDROID_ABI}表示当前正在编译的ABI架构名称。
  • Eclipse下使用workspace_loc来表示当前Eclipse的工作目录。如果要指定当前工作目录下某个项目的src文件夹路径,则可使用:"${workspace_loc:/project_name}/src"

各平台汇编语言写法

  • MASM x86
; 汇编源文件func.asm

.model flat
.code

_CFunc   proc public

    mov     eax, [esp + 4]    ; EAX存放第一个参数
    mov     ecx, [esp + 8]    ; ECX存放第二个参数
    sub     eax, ecx
    ret

_CFunc   endp


_CFunc2     proc public

    mov     edx, 12345678H    ; 存放高4字节
    mov     eax, 90abcdefH    ; 存放低4字节
    ret

_CFunc2     endp


_CFunc3     proc public

    push    40          ; 给成员d赋值
    push    30          ; 给成员c赋值
    push    20          ; 给成员b赋值
    push    10          ; 给成员a赋值
    mov     eax, esp    ; 将当前栈指针赋给EAX作为所返回结构体对象的起始地址
    add     esp, 16     ; 将栈指针恢复到返回地址处
    ret

_CFunc3     endp


@FastFunc@16   proc public

    mov     eax, ecx
    sub     eax, edx
    mov     ecx, [esp + 4]
    mov     edx, [esp + 8]
    sub     ecx, edx
    sub     eax, ecx

    ; 这里用ret 8是将压栈的两个参数在函数返回后直接推出栈
    ; 相当于: ret;    add esp, 8;
    ret     8

@FastFunc@16   endp

end
  • MASM x86-64
.code

; int MyNativeSub(int a, int b);

MyNativeSub     proc export

    sub     ecx, edx
    mov     eax, ecx
    ret

MyNativeSub endp

end
  • MASM arm64
; int MyNativeSub(int a, int b);

MyNativeSub     proc export

    sub     w0, w0, w1
    ret

    endp

end
  • GAS x86-64
.text
.align 4
.intel_syntax noprefix
 
.globl MyASMTest
 
MyASMTest:
 
    sub     edi, esi
    mov     eax, edi
    ret
  • GAS arm64
.text
.align 4
 
.globl MyASMTest
 
MyASMTest:
 
    sub     w0, w0, w1
    ret
  • NASM x86-64
; 这是一个汇编文件
; YASM的注释风格使用分号形式
 
global MyASMTest
 
section .text

bits 64
 
MyASMTest:
 
    sub     edi, esi
    mov     eax, edi
    ret

GAS局部跳转标签

GAS中的局部跳转标签又称为局部符号名Local Symbol Name ),用于帮助编译器和程序员临时地使用名字。这个对于内联汇编以及汇编中的宏里使用跳转标签的场合会显得格外有用。GAS中提供了10个局部符号名,分别是数字 09。要引用最近先前已经定义好的符号,使用 Nb 这种形式。要引用一个临时符号的下一个定义,写作为 Nf。这里的 N 即表示所定义的临时符号名。而f代表“forwards”,b则代表“backwards”。

至于你如何使用这些局部标签,没有什么约束。但记住,在汇编文件中的任一点,你只能引用最多10个前向局部标签以及最多10个后向局部标签。

局部标签名仅仅是个符号设备。在汇编器使用它们之前,它们会立即被变换为更为常规的符号。这些符号名被存放在符号表中,将会随着错误消息以及可选地发射到含有这些部分的目标文件中:

  • L:所有局部标签会以“L”打头。
  • 数字:局部标签名。
  • C-A:包含不寻常的字符。
  • 序数:这是一个序列号以保证标签的唯一性。

例如,第一个 1: 可能被命名为:“L1C-A1”;第44个 3: 可能被命名为:“L3C-A44”。

下面举一个x86-64汇编代码的例子:

.text
.align 4
.intel_syntax noprefix

.globl ASMTest

// uint ASMTest(uint arg)
ASMTest:

    mov     ecx, edi
    xor     eax, eax
    
0:
    inc     eax
    dec     ecx
    jnz     0b
    
    cmp     eax, 0
    je      0f
    
1:
    dec     eax
    dec     edi
    jnz     1b
    
    cmp     eax, 0
    je      1f
    
0:
    add     eax, 10
    
1:
    add     eax, 100
    
    ret

对上述代码调用 ASMTest(10) 的结果为100。


MASM中使用局部跳转标签

MASM中的局部跳转标签用法与GAS类似,只不过它不像GAS里有10种命名,它只有一种,即:@@:。如果要向上引用局部标签,使用 @b;如果向下引用局部标签,使用 @f。下面举一个实际例子:

.code

; int ASMLabelTest(int a)
ASMLabelTest    proc public

    xor     eax, eax

@@:
    inc     eax
    dec     ecx
    jnz     @b

    test    eax, eax
    jne     @f

    add     eax, 10

@@:
    add     eax, 100
    ret

ASMLabelTest    endp

end

如果调用了 ASMLabelTest(10),那么结果为110。


关于x86平台下的栈帧

在x86以及x86-64执行环境下,有不少系统函数会对当前栈帧进行检查或甚至会将某些临时变量分配到我们当前调用函数的栈帧中,尤其是可变参数个数的系统函数调用(比如printf)。此时,我们就需要在自己当前函数中空出一定的栈空间作为栈帧,供给被调函数使用,同时也需要将 RBP 寄存器进行保存。比如以下代码:

#include <stdio.h>

extern void ASMCallFunction(void (*pFunc)(int, int), int a, int b);

static void MyPrintSum(int a, int b)
{
    printf("Welcome to my C function! And the sum of a and b is: %d\n", a + b);
}

int main(void)
{
    ASMCallFunction(MyPrintSum, 10, 6);
}

上述代码中我们在main函数里调用的 ASMCallFunction 函数在以下asm文件中定义:

.code

; void ASMCallFunction(void (*pFunc)(int, int), int a, int b)
ASMCallFunction     proc public

    push    rbp
    mov     rbp, rsp
    sub     rsp, 32

    mov     rax, rcx
    mov     ecx, edx
    mov     edx, r8d
    call    rax

    mov     rsp, rbp
    pop     rbp
    ret

ASMCallFunction     endp

end

从上述代码我们可以看到,我们在 ASMCallFunction 函数中保留了32个字节的栈空间作为栈帧。由于在MSVC的Release模式下执行上面程序时,留24个字节仍然不够,32个字节正好;而在Debug模式下16个字节也没问题。因此这里就选用了32个字节作为其栈空间。另外这里再提一下,x86-64的ABI中一般规定栈指针(RSP)的值应当至少为8字节对齐。


NEON中采用对齐方式的VLD/VST

在ARM官方文档上给出的VLD系列的指令形式为——

instruction

而在ARM GCC中,若要使用对齐方式的话,照着上图的格式写,不管怎么写都不会正确。

下面将介绍在ARM GCC中正确使用对齐方式的NEON读写方法:

.text
.align 4
.globl my_test

.arm

my_test:

    vld1.32 {d0}, [r0, :64]
    vst1.32 {d0}, [r1, :64]
    bx      lr

上述的对齐方式为64位,即8字节对齐。