[PATCH] linux-user: add support for the AARCH64 ILP32 ABI

Marek Szyprowski posted 1 patch 1 month ago
configs/targets/aarch64_ilp32-linux-user.mak | 10 +++
linux-user/aarch64/syscall_64.tbl            |  2 +-
linux-user/aarch64/target_mman.h             |  6 ++
linux-user/elfload.c                         |  6 ++
linux-user/qemu.h                            |  4 +-
linux-user/syscall.c                         |  6 +-
linux-user/syscall_defs.h                    | 65 +++++++++++++++++++-
linux-user/user-internals.h                  |  4 +-
scripts/qemu-binfmt-conf.sh                  |  6 +-
target/arm/cpu-param.h                       |  6 +-
10 files changed, 103 insertions(+), 12 deletions(-)
create mode 100644 configs/targets/aarch64_ilp32-linux-user.mak
[PATCH] linux-user: add support for the AARCH64 ILP32 ABI
Posted by Marek Szyprowski 1 month ago
This patch adds support for the AARCH64 ILP32 ABI [1] to the QEMU
linux-user AARCH64 port.

The ILP32 ABI was initially developed quite some time ago [2] to
facilitate porting legacy code to the new AARCH64 architecture. However,
it appears that most legacy code is still used as ARMv7 (ARM 32-bit)
binaries, running on ARM 64-bit CPUs through the 32-bit EL0 compatibility
feature of those CPUs. As a result, the ILP32 ABI has not been widely
adopted.

The 32-bit EL0 compatibility feature is optional, and it seems that
upcoming ARM 64-bit CPUs will not include it [3]. Therefore, the AARCH64
ILP32 ABI can be revived to support running older legacy code. The ILP32
ABI can also be beneficial on systems with tight memory constraints, as
32-bit code typically consumes less memory, both in terms of operation
and code size, compared to the same code using a 64-bit ABI.

This indicates that there are still important use cases for the AARCH64
ILP32 ABI. Adding support for such binaries in the QEMU linux-user
enables common development scenarios, such as simulating a build-system
with AARCH64 ILP32 ABI instead of relying on explicit cross-compilation.

The qemu-aarch64_ilp32 target as been tested with tests from the Linux
Test Project [4] copiled with the toolchain released once by the Linaro
[5]. The results are very similar to qemu-arm and other 32bit linux-user
targets. Manual inspection of the failures didn't reveal any failures
specific to AARCH64 ILP32 ABI version.

[1] https://github.com/ARM-software/abi-aa/blob/main/aaelf64/aaelf64.rst
[2] https://lore.kernel.org/all/20180516081910.10067-1-ynorov@caviumnetworks.com/
[3] https://developer.arm.com/documentation/109697/2024_12/Feature-descriptions/The-Armv9-0-architecture-extension?lang=en
[4] https://github.com/linux-test-project/ltp
[5] https://snapshots.linaro.org/components/toolchain/binaries/7.3-2018.04-rc1/aarch64-linux-gnu_ilp32/

Signed-off-by: Marek Szyprowski <m.szyprowski@samsung.com>
---
 configs/targets/aarch64_ilp32-linux-user.mak | 10 +++
 linux-user/aarch64/syscall_64.tbl            |  2 +-
 linux-user/aarch64/target_mman.h             |  6 ++
 linux-user/elfload.c                         |  6 ++
 linux-user/qemu.h                            |  4 +-
 linux-user/syscall.c                         |  6 +-
 linux-user/syscall_defs.h                    | 65 +++++++++++++++++++-
 linux-user/user-internals.h                  |  4 +-
 scripts/qemu-binfmt-conf.sh                  |  6 +-
 target/arm/cpu-param.h                       |  6 +-
 10 files changed, 103 insertions(+), 12 deletions(-)
 create mode 100644 configs/targets/aarch64_ilp32-linux-user.mak

diff --git a/configs/targets/aarch64_ilp32-linux-user.mak b/configs/targets/aarch64_ilp32-linux-user.mak
new file mode 100644
index 0000000000..e70fd98eef
--- /dev/null
+++ b/configs/targets/aarch64_ilp32-linux-user.mak
@@ -0,0 +1,10 @@
+TARGET_ARCH=aarch64
+TARGET_BASE_ARCH=arm
+TARGET_XML_FILES= gdb-xml/aarch64-core.xml gdb-xml/aarch64-fpu.xml gdb-xml/aarch64-pauth.xml gdb-xml/aarch64-mte.xml
+TARGET_HAS_BFLT=y
+CONFIG_SEMIHOSTING=y
+CONFIG_ARM_COMPATIBLE_SEMIHOSTING=y
+TARGET_SYSTBL_ABI=common,32,time32,stat64,renameat,rlimit,memfd_secret
+TARGET_SYSTBL=syscall_64.tbl
+TARGET_LONG_BITS=64
+TARGET_ABI32=y
diff --git a/linux-user/aarch64/syscall_64.tbl b/linux-user/aarch64/syscall_64.tbl
index 845e24eb37..33507d823b 100644
--- a/linux-user/aarch64/syscall_64.tbl
+++ b/linux-user/aarch64/syscall_64.tbl
@@ -265,7 +265,7 @@
 221	common	execve				sys_execve			compat_sys_execve
 222	32	mmap2				sys_mmap2
 222	64	mmap				sys_mmap
-223	32	fadvise64_64			sys_fadvise64_64		compat_sys_fadvise64_64
+223	32	arm_fadvise64_64		sys_arm_fadvise64_64
 223	64	fadvise64			sys_fadvise64_64
 224	common	swapon				sys_swapon
 225	common	swapoff				sys_swapoff
diff --git a/linux-user/aarch64/target_mman.h b/linux-user/aarch64/target_mman.h
index 69ec5d5739..7356235a40 100644
--- a/linux-user/aarch64/target_mman.h
+++ b/linux-user/aarch64/target_mman.h
@@ -4,6 +4,7 @@
 #define TARGET_PROT_BTI         0x10
 #define TARGET_PROT_MTE         0x20
 
+#ifndef TARGET_ABI32
 /*
  * arch/arm64/include/asm/processor.h:
  *
@@ -16,6 +17,11 @@
 
 /* arch/arm64/include/asm/elf.h */
 #define ELF_ET_DYN_BASE         TARGET_PAGE_ALIGN((1ull << 48) / 3 * 2)
+#else
+/* aarch64_ilp32 */
+#define TASK_UNMAPPED_BASE      (1ull << (30))
+#define ELF_ET_DYN_BASE         TARGET_PAGE_ALIGN((1ull << 30) / 3 * 2)
+#endif
 
 #include "../generic/target_mman.h"
 
diff --git a/linux-user/elfload.c b/linux-user/elfload.c
index a2c152e5ad..70d2913915 100644
--- a/linux-user/elfload.c
+++ b/linux-user/elfload.c
@@ -682,7 +682,11 @@ static const VdsoImageInfo *vdso_image_info(uint32_t elf_flags)
 /* 64 bit ARM definitions */
 
 #define ELF_ARCH        EM_AARCH64
+#ifndef TARGET_ABI32
 #define ELF_CLASS       ELFCLASS64
+#else
+#define ELF_CLASS       ELFCLASS32
+#endif
 #if TARGET_BIG_ENDIAN
 # define ELF_PLATFORM    "aarch64_be"
 #else
@@ -977,11 +981,13 @@ const char *elf_hwcap2_str(uint32_t bit)
 
 #undef GET_FEATURE_ID
 
+#ifndef TARGET_ABI32
 #if TARGET_BIG_ENDIAN
 # define VDSO_HEADER  "vdso-be.c.inc"
 #else
 # define VDSO_HEADER  "vdso-le.c.inc"
 #endif
+#endif
 
 #endif /* not TARGET_AARCH64 */
 
diff --git a/linux-user/qemu.h b/linux-user/qemu.h
index 5f00750151..1acf50b2b7 100644
--- a/linux-user/qemu.h
+++ b/linux-user/qemu.h
@@ -86,7 +86,7 @@ struct vm86_saved_state {
 };
 #endif
 
-#if defined(TARGET_ARM) && defined(TARGET_ABI32)
+#if defined(TARGET_ARM) && defined(TARGET_ABI32) && !defined(TARGET_AARCH64)
 /* FPU emulator */
 #include "nwfpe/fpa11.h"
 #endif
@@ -98,7 +98,7 @@ struct emulated_sigtable {
 
 struct TaskState {
     pid_t ts_tid;     /* tid (or pid) of this task */
-#ifdef TARGET_ARM
+#if defined(TARGET_ARM) && !defined(TARGET_AARCH64)
 # ifdef TARGET_ABI32
     /* FPA state */
     FPA11 fpa;
diff --git a/linux-user/syscall.c b/linux-user/syscall.c
index 02ea4221c9..1012686227 100644
--- a/linux-user/syscall.c
+++ b/linux-user/syscall.c
@@ -6936,7 +6936,7 @@ static inline abi_long copy_to_user_flock(abi_ulong target_flock_addr,
 typedef abi_long from_flock64_fn(struct flock *fl, abi_ulong target_addr);
 typedef abi_long to_flock64_fn(abi_ulong target_addr, const struct flock *fl);
 
-#if defined(TARGET_ARM) && TARGET_ABI_BITS == 32
+#if defined(TARGET_ARM) && TARGET_ABI_BITS == 32 && !defined(TARGET_AARCH64)
 struct target_oabi_flock64 {
     abi_short l_type;
     abi_short l_whence;
@@ -7642,7 +7642,7 @@ static inline abi_long host_to_target_stat64(CPUArchState *cpu_env,
                                              abi_ulong target_addr,
                                              struct stat *host_st)
 {
-#if defined(TARGET_ARM) && defined(TARGET_ABI32)
+#if defined(TARGET_ARM) && defined(TARGET_ABI32) && !defined(TARGET_AARCH64)
     if (cpu_env->eabi) {
         struct target_eabi_stat64 *target_st;
 
@@ -12533,7 +12533,7 @@ static abi_long do_syscall1(CPUArchState *cpu_env, int num, abi_long arg1,
         from_flock64_fn *copyfrom = copy_from_user_flock64;
         to_flock64_fn *copyto = copy_to_user_flock64;
 
-#ifdef TARGET_ARM
+#if defined(TARGET_ARM) && !defined(TARGET_AARCH64)
         if (!cpu_env->eabi) {
             copyfrom = copy_from_user_oabi_flock64;
             copyto = copy_to_user_oabi_flock64;
diff --git a/linux-user/syscall_defs.h b/linux-user/syscall_defs.h
index 86d773add7..292939575c 100644
--- a/linux-user/syscall_defs.h
+++ b/linux-user/syscall_defs.h
@@ -60,7 +60,7 @@
 #define TARGET_IOC_TYPEBITS     8
 
 #if (defined(TARGET_I386) && defined(TARGET_ABI32))                     \
-    || (defined(TARGET_ARM) && defined(TARGET_ABI32))                   \
+    || (defined(TARGET_ARM) && defined(TARGET_ABI32) && !defined(TARGET_AARCH64))                   \
     || (defined(TARGET_SPARC) && defined(TARGET_ABI32))                 \
     || defined(TARGET_M68K) || defined(TARGET_SH4)
 /* 16 bit uid wrappers emulation */
@@ -1234,7 +1234,7 @@ struct target_winsize {
 #include "target_mman.h"
 
 #if (defined(TARGET_I386) && defined(TARGET_ABI32))     \
-    || (defined(TARGET_ARM) && defined(TARGET_ABI32))
+    || (defined(TARGET_ARM) && defined(TARGET_ABI32) && !defined(TARGET_AARCH64))
 #define TARGET_STAT_HAVE_NSEC
 struct target_stat {
     abi_ushort st_dev;
@@ -1905,7 +1905,7 @@ struct target_stat {
     abi_long       st_blocks;
     abi_ulong  __unused[3];
 };
-#elif defined(TARGET_AARCH64)
+#elif defined(TARGET_AARCH64) && !defined(TARGET_ABI32)
 #define TARGET_STAT_HAVE_NSEC
 struct target_stat {
     abi_ulong  st_dev;
@@ -1928,6 +1928,65 @@ struct target_stat {
     abi_ulong  target_st_ctime_nsec;
     abi_uint __unused[2];
 };
+#elif defined(TARGET_AARCH64) && defined(TARGET_ABI32)
+#define TARGET_STAT_HAVE_NSEC
+struct target_stat {
+    abi_ulong  st_dev;
+    abi_ulong  __ilp32_pad1;
+    abi_ulong  st_ino;
+    abi_ulong  __ilp32_pad2;
+    abi_uint   st_mode;
+    abi_uint   st_nlink;
+    abi_uint   st_uid;
+    abi_uint   st_gid;
+    abi_ulong  st_rdev;
+    abi_ulong  __ilp32_pad3;
+    abi_ulong  _pad1;
+    abi_ulong  __ilp32_pad4;
+    abi_long  st_size;
+    abi_ulong  __ilp32_pad5;
+    abi_int    st_blksize;
+    abi_int    __pad2;
+    abi_long   st_blocks;
+    abi_ulong __ilp32_pad6;
+    abi_long  target_st_atime;
+    abi_ulong  target_st_atime_nsec;
+    abi_long  target_st_mtime;
+    abi_ulong  target_st_mtime_nsec;
+    abi_long  target_st_ctime;
+    abi_ulong  target_st_ctime_nsec;
+    abi_uint __unused[2];
+};
+
+#define TARGET_HAS_STRUCT_STAT64
+struct target_stat64 {
+    abi_ulong  st_dev;
+    abi_ulong  __ilp32_pad1;
+#define TARGET_STAT64_HAS_BROKEN_ST_INO 1
+    abi_ulong  __st_ino;
+    abi_ulong  __ilp32_pad2;
+    abi_uint   st_mode;
+    abi_uint   st_nlink;
+    abi_uint   st_uid;
+    abi_uint   st_gid;
+    abi_ulong  st_rdev;
+    abi_ulong  __ilp32_pad3;
+    abi_ulong  __pad1;
+    abi_ulong  __ilp32_pad4;
+    abi_llong  st_size;
+    abi_int    st_blksize;
+    abi_int    _pad2;
+    abi_long   st_blocks;
+    abi_ulong  __ilp32_pad5;
+    abi_long   target_st_atime;
+    abi_ulong  target_st_atime_nsec;
+    abi_long   target_st_mtime;
+    abi_ulong  target_st_mtime_nsec;
+    abi_long   target_st_ctime;
+    abi_ulong  target_st_ctime_nsec;
+    abi_ullong st_ino;
+} QEMU_PACKED;
+
 #elif defined(TARGET_XTENSA)
 #define TARGET_STAT_HAVE_NSEC
 struct target_stat {
diff --git a/linux-user/user-internals.h b/linux-user/user-internals.h
index b9b05c1d11..b13ca36577 100644
--- a/linux-user/user-internals.h
+++ b/linux-user/user-internals.h
@@ -130,8 +130,10 @@ static inline uint64_t target_offset64(uint64_t word0, uint64_t word1)
 
 void print_termios(void *arg);
 
+#if (TARGET_ABI_BITS == 32) && defined(TARGET_AARCH64)
+static inline int regpairs_aligned(CPUArchState *cpu_env, int num) { return 1; }
 /* ARM EABI and MIPS expect 64bit types aligned even on pairs or registers */
-#ifdef TARGET_ARM
+#elif TARGET_ARM
 static inline int regpairs_aligned(CPUArchState *cpu_env, int num)
 {
     return cpu_env->eabi;
diff --git a/scripts/qemu-binfmt-conf.sh b/scripts/qemu-binfmt-conf.sh
index 6ef9f118d9..695d15e6ea 100755
--- a/scripts/qemu-binfmt-conf.sh
+++ b/scripts/qemu-binfmt-conf.sh
@@ -104,6 +104,10 @@ aarch64_be_magic='\x7fELF\x02\x02\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0
 aarch64_be_mask='\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff'
 aarch64_be_family=armeb
 
+aarch64_ilp32_magic='\x7fELF\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\xb7\x00'
+aarch64_ilp32_mask='\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff'
+aarch64_ilp32_family=arm
+
 hppa_magic='\x7f\x45\x4c\x46\x01\x02\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x0f'
 hppa_mask='\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff'
 hppa_family=hppa
@@ -159,7 +163,7 @@ qemu_get_family() {
     ppc64el|ppc64le)
         echo "ppcle"
         ;;
-    arm|armel|armhf|arm64|armv[4-9]*l|aarch64)
+    arm|armel|armhf|arm64|armv[4-9]*l|aarch64|aarch64_ilp32)
         echo "arm"
         ;;
     armeb|armv[4-9]*b|aarch64_be)
diff --git a/target/arm/cpu-param.h b/target/arm/cpu-param.h
index 896b35bd6d..151d512e91 100644
--- a/target/arm/cpu-param.h
+++ b/target/arm/cpu-param.h
@@ -8,9 +8,13 @@
 #ifndef ARM_CPU_PARAM_H
 #define ARM_CPU_PARAM_H
 
-#ifdef TARGET_AARCH64
+#if defined(TARGET_AARCH64) && !defined(TARGET_ABI32)
 # define TARGET_PHYS_ADDR_SPACE_BITS  52
 # define TARGET_VIRT_ADDR_SPACE_BITS  52
+#elif defined(TARGET_AARCH64) && defined(TARGET_ABI32)
+# define TARGET_LONG_BITS             64
+# define TARGET_PHYS_ADDR_SPACE_BITS  40
+# define TARGET_VIRT_ADDR_SPACE_BITS  32
 #else
 # define TARGET_PHYS_ADDR_SPACE_BITS  40
 # define TARGET_VIRT_ADDR_SPACE_BITS  32
-- 
2.34.1
Re: [PATCH] linux-user: add support for the AARCH64 ILP32 ABI
Posted by Peter Maydell 1 month ago
On Tue, 25 Feb 2025 at 12:44, Marek Szyprowski <m.szyprowski@samsung.com> wrote:
>
> This patch adds support for the AARCH64 ILP32 ABI [1] to the QEMU
> linux-user AARCH64 port.
>
> The ILP32 ABI was initially developed quite some time ago [2] to
> facilitate porting legacy code to the new AARCH64 architecture. However,
> it appears that most legacy code is still used as ARMv7 (ARM 32-bit)
> binaries, running on ARM 64-bit CPUs through the 32-bit EL0 compatibility
> feature of those CPUs. As a result, the ILP32 ABI has not been widely
> adopted.

Firm "no" on adding this to the QEMU linux-user mode emulation
until/unless the ILP32 ABI is supported in mainline Linux
kernels, I'm afraid (which I do not expect it ever will be).

thanks
-- PMM
Re: [PATCH] linux-user: add support for the AARCH64 ILP32 ABI
Posted by Richard Henderson 1 month ago
On 2/25/25 04:58, Peter Maydell wrote:
> On Tue, 25 Feb 2025 at 12:44, Marek Szyprowski <m.szyprowski@samsung.com> wrote:
>>
>> This patch adds support for the AARCH64 ILP32 ABI [1] to the QEMU
>> linux-user AARCH64 port.
>>
>> The ILP32 ABI was initially developed quite some time ago [2] to
>> facilitate porting legacy code to the new AARCH64 architecture. However,
>> it appears that most legacy code is still used as ARMv7 (ARM 32-bit)
>> binaries, running on ARM 64-bit CPUs through the 32-bit EL0 compatibility
>> feature of those CPUs. As a result, the ILP32 ABI has not been widely
>> adopted.
> 
> Firm "no" on adding this to the QEMU linux-user mode emulation
> until/unless the ILP32 ABI is supported in mainline Linux
> kernels, I'm afraid (which I do not expect it ever will be).

The partial support for aarch64 ilp32 that was added to glibc is being removed now.  It 
brought up the conversation about removing ilp32 support from gcc + binutils in the next 
gcc development cycle.


r~