abi linux

2018-09-20 07:51:41 juS3Ve 阅读数 1003
640?wx_fmt=png

今天我们来看看Linux中形形色色的接口


前言

如果将内核比作一座工厂,那么Linux中众多的接口就是通往这个巨大工厂的高速公路。这条路要足够坚固,禁得起各种破坏(Robust)。要能跑得了运货的卡车,还要能升降飞机。(Compatible)。当然了这条路要越宽越好(Performant)。如下图所标,Linux中有四种类型的接口。位于内核和用户之间的API(应用程序接口)和ABI(应用二进制接口)。内核内部的API和ABI。下面我们逐条的来看看这些接口。

640?wx_fmt=png


1.Linux API

内核-用户接口有内核系统调用和GNU C Library (glibc)的包装例程(wrap subroutines)构成。Linux API开发的目标是给POSIX标准提供一个相对兼容的,程序健壮性强的,高性能的实现,同时又实现一些Linux特有的标准。

640?wx_fmt=png


 1.1 Linux内核的系统调用接口

系统调用接口包含了内核中所有已经实现的和可使用的系统调用。

640?wx_fmt=png

1.2 C标准库

标准库是对系统调用的封装,用于给用户提供一个简单易用的用户态接口。其中最为熟知的就是GNU C Library(glibc),还有为嵌入式和移动设备使用的uClibc。



640?wx_fmt=png

1.3对POSIX的补充

还有一些接口是Linux特有的,比如:

Cgroup子系统,该子系统包含cgroup系统调用和libcgroup。


Futex,epoll,dnotify,inotify,Fanotify等系统调用。


2. Linux ABI


ABI是一系列约定的集合,例如GNU/Linux,FreeBSD约定函数调用的头六个整型参数放在寄存器RDI, RSI, RDX, RCX, R8和R9上;同时XMM0到XMM7用来放置浮点变元。对于系统调用,R10用来替代RCX。其他额外的参数推入栈,返回值保存在RAX中。[1] 可以说调用惯例(calling convention)就是ABI。因此,ABI是和具体CPU架构和OS相关的。


具体而言,ABI包含以下内容:[3]

1. 一个特定的处理器指令集

2. 函数调用惯例

3. 系统调用方式

4. 可执行文件的格式(ELF,PE)


那么,究竟我们为什么要纠结于ABI这个概念呢?答案是为了兼容,只要OS遵守相同的ABI规范,那么不同的应用就可以实现向前兼容,再也不用担心版本升级后,旧版本的应用不能运行了。



640?wx_fmt=png


3. In-kernel API

内核API主要是内核中标记为 “EXPORT_SYMBOL”的函数。这些函数主要是为了内核模块的编写而提供的。收到内核版本迭代的影响,内核API并不稳定。2.x版本内核的模块可能在3.x版本上就无法使用。


640?wx_fmt=png

4. In-Kernel ABI

内核的abi和linux abi的定义类似,再次不再赘述。


5. Abstraction ABI

在某些情况下,内核过于底层,开发者需要更高一层的抽象。于是出现了类似Mesa 3D的为图形驱动开发而生的API。

640?wx_fmt=png



参考

[1] 

https://en.wikipedia.org/wiki/Linux_kernel_interfaces

所有图的来源

[2]

https://abi-laboratory.pro/index.php?view=abi-view

[3] https://en.wikipedia.org/wiki/Application_binary_interface

abi的wiki

[4] https://zh.wikipedia.org/wiki/X86%E8%B0%83%E7%94%A8%E7%BA%A6%E5%AE%9A

x86调用惯例的中文wiki

[5] 

http://laoar.net/blogs/316/

讨论什么是ABI

[6] https://elixir.bootlin.com/linux/latest/source/kernel/sched/wait.c

内核API

[7]

https://stackoverflow.com/questions/2171177/what-is-an-application-binary-interface-abi

讨论什么是ABI


查看"Linux阅码场"精华技术文章请移步:

Linux阅码场原创精华文章汇总


扫描二维码关注"Linux阅码场" 

640?wx_fmt=png

2018-06-07 13:30:46 xuejianbest 阅读数 598

本篇文章是基于C和汇编的

首先采用调用0x80中断的方式进行系统调用:

void prints(char *str){
    int i = 0;
    while(str[i] != '\0'){
        i++;
    }

    asm("movl $4, %%eax \n\t"
        "movl $1, %%ebx \n\t"
        "movl %0, %%ecx \n\t"
        "movl %1, %%edx \n\t"
        "int $0x80 \n\t"
        ::"m"(str), "m"(i)
      );

    return;
}

int main(){
    char *str1  = "ab"; //字符串内容存储在堆上
    char str2[] = "ab"; //字符串内容存储在栈上
    prints(str1);  //正常显示
    prints(str2);  //不显示
}

这里的关键在于Linux系统0x80号中断是32位系统调用。其接收4个参数,寄存器ecx中的参数为要显示的内容的内存首地址,而在64位系统下用gcc编译,生成的程序默认是64位的,64位程序的内存地址(也就是C语言中的指针)是64位的,我们把内存地址传给ecx,ecx只有32位,所以地址的高32位会丢失,系统按照错误的地址去寻址,肯定找不到内容。

那么为何堆上的数据可以显示,段上的不行呢,因为Linux系统在给程序分配内存空间的时候,堆的内存位置位于低内存地址,地址虽然是64位的,但是高32位全为0;而堆栈却位于高内存地址,高32位不为零,因此只能正确寻找到堆上的数据。

0x80中断传入不正确的地址会怎样:并不会报错,没有任何提示,但是会返回一个错误码(负数),存在eax中,具体错误码可以在头文件中查看(头文件中是正数):

/usr/include/asm-generic/errno-base.h 
/usr/include/asm-generic/errno.h

通过echo $?可以看到程序返回242(0xF2),也就是-14(负数代表调用出错,绝对值是错误码;若正确调用会返回正确输出的字符数)。查看errno-base.h中定义:

#define    EFAULT        14    /* Bad address */

和上面分析的一样:传入了错误的内存地址。

实际上printf正确调用也会返回写入(标准输出)的字符数(返回值用eax寄存器传递),错误调用返回负数,这点和系统调用的返回值类似。


我们怎么修改我们的程序呢?64位程序不再用int $0x80进行系统调用,可以考虑用syscall指令,这是64位系统的ABI调用方式,系统调用号和0x80不同,参数传递的寄存器约定也不一样。
详情可以查看:http://blog.rchapman.org/posts/Linux_System_Call_Table_for_x86_64/

修改后的程序:

void prints(char *str){
    long i = 0;  //这里也可以用int,因为用int后面会编译为`movl ..., %edx`,这个语句会把edx高32位清零。
    while(str[i] != '\0'){
        i++;
    }
    asm("syscall \n\t"::"a"(1), "D"(1), "S"(str), "d"(i));
}

int main(){
    char *str = "ab\n";
    char str1[] = "cd\n";

    prints(str);
    prints(str1);
}

syscall参数寄存器约定:

系统调用功能号:rax
参数列表按顺序分别是:rdi、rsi、rdx、r10、r8、r9

普通函数参数寄存器约定:

参数列表按顺序分别是:rdi、rsi、rdx、rcx、r8、r9

2012-08-13 15:42:53 force_eagle 阅读数 3047

New X32 ABI: 64-bit mode with 32-bit pointers

The 64 bits mode of x86 CPUs enlarges the CPU registers to 64 bit, allowing to address larger (>4GB) amounts of memory. This widening, however, has a drawback. Because memory addresses are 64-bit wide, pointers occupy 64 bits of space, the double of space used in 32 bits mode, so binaries compiled for the 64-bit mode are bigger, and when these programs run they use more RAM. And since they are bigger they can cause a performance loss, because with bigger memory addresses, less CPU instructions will fit in the CPU caches.

Some programs have workloads CPU and pointer intensive enough to care about this performance, but with memory requirements not big enough to care about 64-bit memory addressing. They can avoid the 64-bit pointer overhead by just using the 32 bits mode: Processors still allow to run 32-bit operative systems, or run 32-bit programs on top of 64-bit kernels. But this choice also has problems. When a program runs in 32-bit mode, it loses all the other features of the 64-bit mode: larger number of CPU registers, better floating-point performance, faster PIC (position-independent code) shared libraries, function parameters passed via registers, faster syscall instruction...

So a new X32 kernel ABI has been created. A program compiled for this new ABI can run in the 64-bit mode, with all its features, but it uses 32 bits pointers and 32-bit long C type. So applications who need it can enjoy the performance of the 64-bit mode, but with the memory requirements of a 32 bits ABI. Code: (commit)

Recommended LWN article: The x32 system call ABI

Slides from the developers: link

Official X32 coordination site: http://sites.google.com/site/x32abi

2015-07-11 16:56:31 linyt 阅读数 8647

背景

最近工作中遇到个问题,就是多个系统跑着不同版本的内核,不同架构的ARM芯片,上层的业务程序能否二进制归一(共镜像)。

根据自己对Linux的了解,glibc是否可以在ABI层面做到向前兼容。那个整个系统能否做到呢?这勾起了我的分析和思考。

ABI兼容依赖什么

试想一下,一个程序从源代码到最终的二进制,有哪些组件参与了编译和链接过程,大概如下:

  • 内核导出用户态头文件
  • gcc编译器,gcc提供的静态.o/.a,以及它提供的动态库(如libgcc_s.so)
  • glibc
  • 第三方开源库

需要分析各组通能否做到二进制兼容性,以及是如何做到的。下面我对各组件,以思维导图来分析是否能满足ABI兼容性。

Linux应用程序ABI兼容性分析

上面的思维导图把我想到的组件都进行细分,把每个细节都覆盖到。当然,还会有一些细节是我没有想到的。下面对不那么常见的组件进行说明:

  • 内核导出头文件: 构建过交叉编译工具链是嵌入式里面常做的事情,工具链的构建需要使用内核导出的导文件。这是内核态和用户约定好的基础类型和数据结构,比如我们常见的time_t,pid_t等等类型。整于用户态软件都以它为基石。
  • glibc版本机制:glibc对大部分符号都打上版本号,日后在高版本的glibc中,由于POSIX/LSB标准的变化,或者函数接口有变,它会创建该符号的另一新版本,但原来版本的符号仍然存在,同时语义和参数保持不变。它通过这种机制来保证ABI的向前兼容性(请注意,无法保证向后的兼容性)

从分析来看,大部分软件都是可以保持向前兼容的。这就是为什么从一台机制上的程序拷贝到另一台机制上,程序还能正常运行。

Linux standard Base(LSB)

噢,这到底是巧合呢?还是什么:所有Linux 程序都是在所有机器上运行。我也带个着这个问题去找一些资料。
想信大家听说到POSIX规范,Linux Standard Base(LSB)规范。起初,我以为LSB是API规范而非ABI规范。当我去深入分析时,发现它是一个ABI规范,它解决两方面的问题:

  • 在发行版Linux A上运行的程序,可以运行在发行版本Linux B上。
  • 应用程序在现在的机器上运行,也能在未来新系统和机器上运行。

最近Linux基金会发起LSB项目就是希望Linux发行商能做到相互兼容,并且也能向前兼容。解决Linux应用的可移植性和兼容性。

LSB实际是是一组ABI接口的定义,它规范了运行环境所需要的类型,宏,变量和函数的二进制接口。

那么,哪些东西受到LSB的制约呢?也即LSB规范约束的范围多广。刚开始,我还以为只有kernel, glibc等一些关键的组件。事实上,打开LSB官网说明会发现它包罗万象,从命令,ELF,glibc接口,到桌面,脚本语言等等都有相关的规定。

谁应该遵守LSB规范

毫无疑问,kernel, glibc等都应该遵守LSB标准。事情上,并非如此。 LSB希望Linux发布版(如Redhat, Suse, Ubuntu)能遵守LSB,因此它提供了LSB的测试套和LSB的认证服务。

那么Kernel和glibc就真的不需要考虑LSB了吧,答案是否定的。整个kernel/glibc和各大Linux知名组件都以开源方式进行开发,它里面的技术早已不是什么密秘了,各大公司都有工程师参与,所有公司都希望这些软件都是遵守LSB,使所有软件都能轻易地融合在一起,不希望让发生行商花很大力气去集成。因此所有软件都是会尽量遵守LSB规范,但无法排除有那么一两个例外的Case。

glibc是大家用得最多的组件,也算是我比较了解的组件之一,到网上找了一些它的兼容性分析,发现它也不能保证100%向前兼容。不能完全兼容的发生在2.16.0和2.13两个版本上。当前对于一般的软件,这个兼容性是完全足够了的。

“向后”兼容性

那么Linux能做到向后兼容性吗?答案是否定的,因为LSB也无法做到向后兼容。

那问题来了,如果我需要做到向后兼容呢?假如有下面这种场景:

很多服务器都是Ubuntu 7.04,算是8年前的服务器了,数量很多。同时也会有服务器使用了12.04,数量较少。但12.04上的编译器较新,支持很多安全功能。架构师的诉求就是:能否基于12.04版本运行环境做开发编译,生成的服务程序能同时运行在7.04和12.04两个服务器上。

我能想到的解决办法:

  • 找到Ubuntu 7.04和12.04两服务器运行软件,所遵守的LSB版本,服务程序只能调用这两LSB完全兼容的接口
  • 在编程和链接服务程序时,可以指定程序使用的LSB标准(Ubuntu 7.04版本的LSB)。

对于第一个方法,个人认为可行,但约束很多。方法二未知可行,后续再分析

这就是“向后”兼容性想讨论的一个问题。它不是真正的向后兼容性,完全是依赖于向前兼容来实现的。

后记

兼容性,完全不是我熟悉的东西,只是工作上遇到问题,做了初步分析。希望通过blog记录我的思考,当某一天,我对这个知识有深入的经验,再跟大家分享。如有写得不正确,或者误导的内容,请指正,谢谢!

2015/7/11 于上海 灿鸿台风来袭

2018-12-09 12:41:18 melody157398 阅读数 113

前言

如果将内核比作一座工厂,那么Linux中众多的接口就是通往这个巨大工厂的高速公路。这条路要足够坚固,禁得起各种破坏(Robust)。要能跑得了运货的卡车,还要能升降飞机。(Compatible)。当然了这条路要越宽越好(Performant)。如下图所标,Linux中有四种类型的接口。位于内核和用户之间的API(应用程序接口)和ABI(应用二进制接口)。内核内部的API和ABI。下面我们逐条的来看看这些接口。

 

1.Linux API

内核-用户接口有内核系统调用和GNU C Library (glibc)的包装例程(wrap subroutines)构成。Linux API开发的目标是给POSIX标准提供一个相对兼容的,程序健壮性强的,高性能的实现,同时又实现一些Linux特有的标准。

1.1 Linux内核的系统调用接口

系统调用接口包含了内核中所有已经实现的和可使用的系统调用。

1.2 C标准库

标准库是对系统调用的封装,用于给用户提供一个简单易用的用户态接口。其中最为熟知的就是GNU C Library(glibc),还有为嵌入式和移动设备使用的uClibc。

 

 

1.3 对POSIX的补充

还有一些接口是Linux特有的,比如:

Cgroup子系统,该子系统包含cgroup系统调用和libcgroup。

 

Futex,epoll,dnotify,inotify,Fanotify等系统调用。

2. Linux ABI

 

ABI是一系列约定的集合,例如GNU/Linux,FreeBSD约定函数调用的头六个整型参数放在寄存器RDI, RSI, RDX, RCX, R8和R9上;同时XMM0到XMM7用来放置浮点变元。对于系统调用,R10用来替代RCX。其他额外的参数推入栈,返回值保存在RAX中。[1] 可以说调用惯例(calling convention)就是ABI。因此,ABI是和具体CPU架构和OS相关的。

 

具体而言,ABI包含以下内容:[3]

1. 一个特定的处理器指令集

2. 函数调用惯例

3. 系统调用方式

4. 可执行文件的格式(ELF,PE)

 

那么,究竟我们为什么要纠结于ABI这个概念呢?答案是为了兼容,只要OS遵守相同的ABI规范,那么不同的应用就可以实现向前兼容,再也不用担心版本升级后,旧版本的应用不能运行了。

 

 

3. In-kernel API

内核API主要是内核中标记为 “EXPORT_SYMBOL”的函数。这些函数主要是为了内核模块的编写而提供的。收到内核版本迭代的影响,内核API并不稳定。2.x版本内核的模块可能在3.x版本上就无法使用。

4. In-Kernel ABI

内核的abi和linux abi的定义类似,再次不再赘述。

  5. Abstraction ABI

在某些情况下,内核过于底层,开发者需要更高一层的抽象。于是出现了类似Mesa 3D的为图形驱动开发而生的API。

ABI和API的区别

阅读数 1913