从 amd64 可执行文件调用 aarch64 共享库,可能使用二进制翻译/QEMU

Posted

技术标签:

【中文标题】从 amd64 可执行文件调用 aarch64 共享库,可能使用二进制翻译/QEMU【英文标题】:Calling aarch64 shared library from amd64 executable, maybe using binary translation/QEMU 【发布时间】:2022-01-05 07:30:36 【问题描述】:

我有一个适用于 Linux 的 aarch64 库,我想在 amd64 Linux 安装中使用它。目前,我知道一种让它工作的方法,即使用 qemu-arm-static 二进制模拟器和我自己编译的 aarch64 可执行文件,它在 aarch64 库上调用 dlopen 并使用它。

令人烦恼的是,将 aarch64executable 与我的 amd64 环境集成很烦人(例如,假设这个 arm64 库来自物联网设备并实时解码特殊视频文件——我应该如何使用我电脑上的原生库来播放它?)。我最终使用了 UNIX 管道,但我真的不喜欢这个解决方案。

有没有办法我可以将 qemu-arm-static 的东西 only 与库一起使用,这样我就可以拥有一个直接调用库的 amd64 可执行文件?如果不是,那么在这两种架构之间进行交互的最佳方式是什么?是管道吗?

【问题讨论】:

【参考方案1】:

我为此实现的解决方案是使用共享内存 IPC。这个解决方案特别好,因为它与固定长度的 C 结构很好地集成在一起,允许您简单地在一端和另一端使用结构。

假设你有一个带有签名uint32_t so_lib_function_a(uint32_t c[2])的函数

您可以在 amd64 库中编写包装函数:uint32_t wrapped_so_lib_function_a(uint32_t c[2])

然后,您创建一个共享内存结构:

typedef struct 
   uint32_t c[2];
   uint32_t ret;
   int turn; // turn = 0 means amd64 library, turn = 1 means arm library
 ipc_call_struct;

像这样初始化一个结构,然后运行shmget(SOME_SHM_KEY, sizeof(ipc_call_struct), IPC_CREAT | 0777);,从中获取返回值,然后获取指向共享内存的指针。然后将初始化的结构体复制到共享内存中。

然后您在 ARM 二进制端运行 shmget(3)shmat(3),同时获得指向共享内存的指针。 ARM 二进制文件运行一个无限循环,等待它的“轮到”。当turn 设置为1 时,amd64 二进制文件将一直阻塞,直到turn0。 ARM 二进制文件将执行该函数,使用共享结构详细信息作为参数,并使用返回值更新共享内存结构。然后 ARM 库会将 turn 设置为 0 并阻塞,直到 turn 再次变为 1,这将允许 amd64 二进制文件执行其操作,直到它准备好再次调用 ARM 函数。

这是一个例子(它可能还没有编译,但它给了你一个大概的想法):

我们的“未知”库:shared.h

#include <stdint.h>

#define MAGIC_NUMBER 0x44E

uint32_t so_lib_function_a(uint32_t c[2]) 
   // Add args and multiplies by MAGIC_NUMBER
   uint32_t ret; 
   for (int i = 0; i < 2; i++) 
     ret += c[i];
   

   ret *= MAGIC_NUMBER;
   return ret; 

挂钩到“未知”库:shared_executor.c

#include <dlfcn.h>
#include <sys/shm.h>
#include <stdint.h>

#define SHM_KEY 22828 // Some random SHM ID

uint32_t (*so_lib_function_a)(uint32_t c[2]);

typedef struct 
   uint32_t c[2];
   uint32_t ret;
   int turn; // turn = 0 means amd64 library, turn = 1 means arm library
 ipc_call_struct;

int main() 
   ipc_call_struct *handle; 

   void *lib_dlopen = dlopen("./shared.so", RTLD_LAZY);
   so_lib_function_a = dlsym(lib_dlopen, "so_lib_function_a");

   // setup shm
   
   int shm_id = shmget(SHM_KEY, sizeof(ipc_call_struct), IPC_CREAT | 0777);
   handle = shmat(shm_id, NULL, 0);

   // We expect the handle to already be initialised by the time we get here, so we don't have to do anything
   
   while (true) 
     if (handle->turn == 1)  // our turn 
       handle->ret = so_lib_function_a(handle->c);
       handle->turn = 0; // hand off for later
     
   

amd64 方面:shm_shared.h

#include <stdint.h>
#include <sys/shm.h>

typedef struct 
   uint32_t c[2];
   uint32_t ret;
   int turn; // turn = 0 means amd64 library, turn = 1 means arm library
 ipc_call_struct;

#define SHM_KEY 22828 // Some random SHM ID

static ipc_call_struct* handle;

void wrapper_init() 
  // setup shm here
  int shm_id = shmget(SHM_KEY, sizeof(ipc_call_struct), IPC_CREAT | 0777);
  handle = shmat(shm_id, NULL, 0);

  // Initialise the handle
  // Currently, we don't want to call the ARM library, so the turn is still zero
  ipc_call_struct temp_handle =  .c=0, .ret=0, .turn=0 ;
  *handle = temp_handle; 

  // you should be able to fork the ARM binary using "qemu-arm-static" here 
  // (and add code for that if you'd like)


uint32_t wrapped_so_lib_function_a(uint32_t c[2]) 
   handle->c = c;
   handle->turn = 1; // hand off execution to the ARM librar
   while (handle->turn != 0)  // wait
   return handle->ret;  

同样,不能保证这段代码甚至可以编译(目前),但只是一个大概的想法。

【讨论】:

即使这个解决方案也不是很理想。为了更好地工作,您应该使用 futex 系统调用进行同步,这样每个程序就不会周期性地最大化 CPU 内核。

以上是关于从 amd64 可执行文件调用 aarch64 共享库,可能使用二进制翻译/QEMU的主要内容,如果未能解决你的问题,请参考以下文章

Linux AMD64 从复制的程序集中调用 C 库函数

[Linux]arm| amd | X86 | aarch64 [转]

平台及架构

如何在ARM64中实现系统调用?

go build 不同系统下的可执行文件

aarch64-linux-gnu-g++ 交叉编译为 arm64 错误