[RFC] Native Library Calls

Yeqi Fu posted 1 patch 11 months, 2 weeks ago
Failed in applying to current master (apply log)
There is a newer version of this series
include/exec/user/native-func.h      |  8 +++
libnative.c                          | 76 ++++++++++++++++++++++++++++
meson.build                          |  8 +++
meson_options.txt                    |  2 +
scripts/make-config-poison.sh        |  4 +-
scripts/meson-buildoptions.sh        |  4 ++
target/arm/helper.c                  | 47 +++++++++++++++++
target/arm/helper.h                  |  6 +++
target/arm/tcg/translate-a64.c       | 32 ++++++++++++
target/arm/tcg/translate.c           | 30 ++++++++++-
target/arm/tcg/translate.h           |  8 +++
target/i386/helper.h                 |  6 +++
target/i386/tcg/translate.c          | 20 ++++++++
target/i386/tcg/user/meson.build     |  1 +
target/i386/tcg/user/native_helper.c | 63 +++++++++++++++++++++++
target/mips/helper.h                 |  6 +++
target/mips/tcg/meson.build          |  1 +
target/mips/tcg/native_helper.c      | 55 ++++++++++++++++++++
target/mips/tcg/translate.c          | 22 +++++++-
19 files changed, 396 insertions(+), 3 deletions(-)
create mode 100644 include/exec/user/native-func.h
create mode 100644 libnative.c
create mode 100644 target/i386/tcg/user/native_helper.c
create mode 100644 target/mips/tcg/native_helper.c
[RFC] Native Library Calls
Posted by Yeqi Fu 11 months, 2 weeks ago
This patch introduces a set of feature instructions for native calls
and provides helpers to translate these instructions to corresponding
native functions. A shared library is also implemented, where native
functions are rewritten as feature instructions. At runtime, user
programs load the shared library, and feature instructions are
executed when native functions are called. This patch is applicable
to user programs with architectures x86, x86_64, arm, aarch64, mips,
and mips64. To build, compile libnative.c into a shared library for
the user program's architecture and run the
'../configure --enable-user-native-call && make' command.

Signed-off-by: Yeqi Fu <fufuyqqqqqq@gmail.com>
---
 include/exec/user/native-func.h      |  8 +++
 libnative.c                          | 76 ++++++++++++++++++++++++++++
 meson.build                          |  8 +++
 meson_options.txt                    |  2 +
 scripts/make-config-poison.sh        |  4 +-
 scripts/meson-buildoptions.sh        |  4 ++
 target/arm/helper.c                  | 47 +++++++++++++++++
 target/arm/helper.h                  |  6 +++
 target/arm/tcg/translate-a64.c       | 32 ++++++++++++
 target/arm/tcg/translate.c           | 30 ++++++++++-
 target/arm/tcg/translate.h           |  8 +++
 target/i386/helper.h                 |  6 +++
 target/i386/tcg/translate.c          | 20 ++++++++
 target/i386/tcg/user/meson.build     |  1 +
 target/i386/tcg/user/native_helper.c | 63 +++++++++++++++++++++++
 target/mips/helper.h                 |  6 +++
 target/mips/tcg/meson.build          |  1 +
 target/mips/tcg/native_helper.c      | 55 ++++++++++++++++++++
 target/mips/tcg/translate.c          | 22 +++++++-
 19 files changed, 396 insertions(+), 3 deletions(-)
 create mode 100644 include/exec/user/native-func.h
 create mode 100644 libnative.c
 create mode 100644 target/i386/tcg/user/native_helper.c
 create mode 100644 target/mips/tcg/native_helper.c

diff --git a/include/exec/user/native-func.h b/include/exec/user/native-func.h
new file mode 100644
index 0000000000..8eaac03299
--- /dev/null
+++ b/include/exec/user/native-func.h
@@ -0,0 +1,8 @@
+#if defined(CONFIG_USER_ONLY)  && defined(CONFIG_USER_NATIVE_CALL)
+#define NATIVE_MEMCPY 0x1001
+#define NATIVE_MEMCMP 0x1002
+#define NATIVE_MEMSET 0x1003
+#define NATIVE_STRCPY 0x1004
+#define NATIVE_STRCMP 0x1005
+#define NATIVE_STRCAT 0x1006
+#endif /* CONFIG_USER_NATIVE_CALL */
diff --git a/libnative.c b/libnative.c
new file mode 100644
index 0000000000..cc65c8270c
--- /dev/null
+++ b/libnative.c
@@ -0,0 +1,76 @@
+#include <stdio.h>
+#include <stdlib.h>
+
+#define NATIVE_MEMCPY 0x1001
+#define NATIVE_MEMCMP 0x1002
+#define NATIVE_MEMSET 0x1003
+#define NATIVE_STRCPY 0x1004
+#define NATIVE_STRCMP 0x1005
+#define NATIVE_STRCAT 0x1006
+
+void *memcpy(void *dest, const void *src, size_t n);
+int memcmp(const void *s1, const void *s2, size_t n);
+void *memset(void *s, int c, size_t n);
+char *strcpy(char *dest, const char *src);
+int strcmp(const char *s1, const char *s2);
+char *strcat(char *dest, const char *src);
+
+#define STR_MACRO(str) #str
+#define STR(num) STR_MACRO(num)
+
+#if defined(TARGET_X86_64) || defined(TARGET_I386)
+
+/* unused opcode */
+#define __PREFIX_INSTR \
+    ".byte 0x0f,0xff;"
+
+#define NATIVE_CALL_EXPR(func) \
+    __PREFIX_INSTR             \
+    ".word " STR(func) ";" : : :
+#endif
+
+#if defined(TARGET_ARM) || defined(TARGET_AARCH64)
+
+/* unused syscall number */
+#define __PREFIX_INSTR \
+    "svc 0xff;"
+
+#define NATIVE_CALL_EXPR(func) \
+    __PREFIX_INSTR             \
+    ".word " STR(func) ";" : : :
+
+#endif
+
+#if defined(TARGET_MIPS) || defined(TARGET_MIPS64)
+
+/* unused bytes in syscall instructions */
+#define NATIVE_CALL_EXPR(func) \
+    ".long " STR((0x1 << 24) + (func << 8) + 0xC) ";" : : :
+
+#endif
+
+void *memcpy(void *dest, const void *src, size_t n)
+{
+    __asm__ volatile(NATIVE_CALL_EXPR(NATIVE_MEMCPY));
+}
+
+int memcmp(const void *s1, const void *s2, size_t n)
+{
+    __asm__ volatile(NATIVE_CALL_EXPR(NATIVE_MEMCMP));
+}
+void *memset(void *s, int c, size_t n)
+{
+    __asm__ volatile(NATIVE_CALL_EXPR(NATIVE_MEMSET));
+}
+char *strcpy(char *dest, const char *src)
+{
+    __asm__ volatile(NATIVE_CALL_EXPR(NATIVE_STRCPY));
+}
+int strcmp(const char *s1, const char *s2)
+{
+    __asm__ volatile(NATIVE_CALL_EXPR(NATIVE_STRCMP));
+}
+char *strcat(char *dest, const char *src)
+{
+    __asm__ volatile(NATIVE_CALL_EXPR(NATIVE_STRCAT));
+}
diff --git a/meson.build b/meson.build
index 0a5cdefd4d..04e99a4f25 100644
--- a/meson.build
+++ b/meson.build
@@ -2012,6 +2012,11 @@ have_virtfs_proxy_helper = get_option('virtfs_proxy_helper') \
     .require(libcap_ng.found(), error_message: 'the virtfs proxy helper requires libcap-ng') \
     .allowed()
 
+have_user_native_call = get_option('user_native_call') \
+    .require(have_user, error_message: 'user_native_call requires user') \
+    .require(targetos == 'linux', error_message: 'user_native_call requires Linux') \
+    .allowed()
+
 if get_option('block_drv_ro_whitelist') == ''
   config_host_data.set('CONFIG_BDRV_RO_WHITELIST', '')
 else
@@ -2853,6 +2858,9 @@ foreach target : target_dirs
       error('Target @0@ is only available on a Linux host'.format(target))
     endif
     config_target += { 'CONFIG_LINUX_USER': 'y' }
+    if have_user_native_call
+      config_target += { 'CONFIG_USER_NATIVE_CALL': 'y' }
+    endif
   elif target.endswith('bsd-user')
     if 'CONFIG_BSD' not in config_host
       if default_targets
diff --git a/meson_options.txt b/meson_options.txt
index 90237389e2..148dfc99d8 100644
--- a/meson_options.txt
+++ b/meson_options.txt
@@ -352,3 +352,5 @@ option('slirp_smbd', type : 'feature', value : 'auto',
 
 option('hexagon_idef_parser', type : 'boolean', value : true,
        description: 'use idef-parser to automatically generate TCG code for the Hexagon frontend')
+option('user_native_call', type : 'feature', value : 'auto',
+       description: 'use native code for user mode emulation')
diff --git a/scripts/make-config-poison.sh b/scripts/make-config-poison.sh
index 1892854261..cf2ba69949 100755
--- a/scripts/make-config-poison.sh
+++ b/scripts/make-config-poison.sh
@@ -5,10 +5,12 @@ if test $# = 0; then
 fi
 
 # Create list of config switches that should be poisoned in common code...
-# but filter out CONFIG_TCG and CONFIG_USER_ONLY which are special.
+# but filter out CONFIG_TCG, CONFIG_USER_ONLY and CONFIG_USER_NATIVE_CALL
+# which are special.
 exec sed -n \
   -e' /CONFIG_TCG/d' \
   -e '/CONFIG_USER_ONLY/d' \
+  -e '/CONFIG_USER_NATIVE_CALL/d' \
   -e '/^#define / {' \
   -e    's///' \
   -e    's/ .*//' \
diff --git a/scripts/meson-buildoptions.sh b/scripts/meson-buildoptions.sh
index 5714fd93d9..9e98db9eb7 100644
--- a/scripts/meson-buildoptions.sh
+++ b/scripts/meson-buildoptions.sh
@@ -173,6 +173,8 @@ meson_options_help() {
   printf "%s\n" '  tpm             TPM support'
   printf "%s\n" '  u2f             U2F emulation support'
   printf "%s\n" '  usb-redir       libusbredir support'
+  printf "%s\n" '  user-native-call'
+  printf "%s\n" '                  use native code for user mode emulation'
   printf "%s\n" '  vde             vde network backend support'
   printf "%s\n" '  vdi             vdi image format support'
   printf "%s\n" '  vduse-blk-export'
@@ -472,6 +474,8 @@ _meson_option_parse() {
     --disable-u2f) printf "%s" -Du2f=disabled ;;
     --enable-usb-redir) printf "%s" -Dusb_redir=enabled ;;
     --disable-usb-redir) printf "%s" -Dusb_redir=disabled ;;
+    --enable-user-native-call) printf "%s" -Duser_native_call=enabled ;;
+    --disable-user-native-call) printf "%s" -Duser_native_call=disabled ;;
     --enable-vde) printf "%s" -Dvde=enabled ;;
     --disable-vde) printf "%s" -Dvde=disabled ;;
     --enable-vdi) printf "%s" -Dvdi=enabled ;;
diff --git a/target/arm/helper.c b/target/arm/helper.c
index 0b7fd2e7e6..03fbc3724b 100644
--- a/target/arm/helper.c
+++ b/target/arm/helper.c
@@ -25,6 +25,7 @@
 #include "sysemu/tcg.h"
 #include "qapi/error.h"
 #include "qemu/guest-random.h"
+#include "exec/cpu_ldst.h"
 #ifdef CONFIG_TCG
 #include "semihosting/common-semi.h"
 #endif
@@ -12045,3 +12046,49 @@ void aarch64_sve_change_el(CPUARMState *env, int old_el,
     }
 }
 #endif
+
+#if defined(CONFIG_USER_ONLY)  && defined(CONFIG_USER_NATIVE_CALL)
+
+#define NATIVE_FN_W_3W()           \
+    target_ulong arg0, arg1, arg2; \
+    arg0 = env->regs[0];           \
+    arg1 = env->regs[1];           \
+    arg2 = env->regs[2];
+
+void helper_native_memcpy(CPUARMState *env)
+{
+    CPUState *cs = env_cpu(env);
+    NATIVE_FN_W_3W();
+    void *ret;
+    void *dest = g2h(cs, arg0);
+    void *src = g2h(cs, arg1);
+    size_t n = (size_t)arg2;
+    ret = memcpy(dest, src, n);
+    env->regs[0] = (target_ulong)h2g(ret);
+}
+
+void helper_native_memcmp(CPUARMState *env)
+{
+    CPUState *cs = env_cpu(env);
+    NATIVE_FN_W_3W();
+    int ret;
+    void *s1 = g2h(cs, arg0);
+    void *s2 = g2h(cs, arg1);
+    size_t n = (size_t)arg2;
+    ret = memcmp(s1, s2, n);
+    env->regs[0] = ret;
+}
+
+void helper_native_memset(CPUARMState *env)
+{
+    CPUState *cs = env_cpu(env);
+    NATIVE_FN_W_3W();
+    void *ret;
+    void *s = g2h(cs, arg0);
+    int c = (int)arg1;
+    size_t n = (size_t)arg2;
+    ret = memset(s, c, n);
+    env->regs[0] = (target_ulong)h2g(ret);
+}
+
+#endif
diff --git a/target/arm/helper.h b/target/arm/helper.h
index 3335c2b10b..57144bf6fb 100644
--- a/target/arm/helper.h
+++ b/target/arm/helper.h
@@ -1038,6 +1038,12 @@ DEF_HELPER_FLAGS_5(gvec_uclamp_s, TCG_CALL_NO_RWG,
 DEF_HELPER_FLAGS_5(gvec_uclamp_d, TCG_CALL_NO_RWG,
                    void, ptr, ptr, ptr, ptr, i32)
 
+#if defined(CONFIG_USER_ONLY)  && defined(CONFIG_USER_NATIVE_CALL)
+DEF_HELPER_1(native_memcpy, void, env)
+DEF_HELPER_1(native_memcmp, void, env)
+DEF_HELPER_1(native_memset, void, env)
+#endif
+
 #ifdef TARGET_AARCH64
 #include "tcg/helper-a64.h"
 #include "tcg/helper-sve.h"
diff --git a/target/arm/tcg/translate-a64.c b/target/arm/tcg/translate-a64.c
index 741a608739..a0ae96a2fd 100644
--- a/target/arm/tcg/translate-a64.c
+++ b/target/arm/tcg/translate-a64.c
@@ -35,6 +35,7 @@
 #include "cpregs.h"
 #include "translate-a64.h"
 #include "qemu/atomic128.h"
+#include "exec/user/native-func.h"
 
 static TCGv_i64 cpu_X[32];
 static TCGv_i64 cpu_pc;
@@ -42,6 +43,11 @@ static TCGv_i64 cpu_pc;
 /* Load/store exclusive handling */
 static TCGv_i64 cpu_exclusive_high;
 
+#if defined(CONFIG_USER_ONLY) && defined(TARGET_AARCH64) && \
+    defined(CONFIG_USER_NATIVE_CALL)
+bool native_call_status;
+#endif
+
 static const char *regnames[] = {
     "x0", "x1", "x2", "x3", "x4", "x5", "x6", "x7",
     "x8", "x9", "x10", "x11", "x12", "x13", "x14", "x15",
@@ -2292,6 +2298,12 @@ static void disas_exc(DisasContext *s, uint32_t insn)
                 gen_exception_insn_el(s, 0, EXCP_UDEF, syndrome, 2);
                 break;
             }
+#if defined(CONFIG_USER_ONLY)  && defined(CONFIG_USER_NATIVE_CALL)
+            else if (imm16 == 0xff) {
+                native_call_status = true;
+                break;
+            }
+#endif
             gen_ss_advance(s);
             gen_exception_insn(s, 4, EXCP_SWI, syndrome);
             break;
@@ -14203,6 +14215,26 @@ static void aarch64_tr_translate_insn(DisasContextBase *dcbase, CPUState *cpu)
     s->fp_access_checked = false;
     s->sve_access_checked = false;
 
+#if defined(CONFIG_USER_ONLY)  && defined(CONFIG_USER_NATIVE_CALL)
+    if (native_call_status) {
+        switch (insn) {
+        case NATIVE_MEMCPY:
+            gen_helper_native_memcpy(cpu_env);
+            break;
+        case NATIVE_MEMCMP:
+            gen_helper_native_memcmp(cpu_env);
+            break;
+        case NATIVE_MEMSET:
+            gen_helper_native_memset(cpu_env);
+            break;
+        default:
+            unallocated_encoding(s);
+        }
+        native_call_status = false;
+        return;
+    }
+#endif
+
     if (s->pstate_il) {
         /*
          * Illegal execution state. This has priority over BTI
diff --git a/target/arm/tcg/translate.c b/target/arm/tcg/translate.c
index 7468476724..9d84cad960 100644
--- a/target/arm/tcg/translate.c
+++ b/target/arm/tcg/translate.c
@@ -34,7 +34,7 @@
 #include "exec/helper-gen.h"
 #include "exec/log.h"
 #include "cpregs.h"
-
+#include "exec/user/native-func.h"
 
 #define ENABLE_ARCH_4T    arm_dc_feature(s, ARM_FEATURE_V4T)
 #define ENABLE_ARCH_5     arm_dc_feature(s, ARM_FEATURE_V5)
@@ -58,6 +58,11 @@ TCGv_i32 cpu_CF, cpu_NF, cpu_VF, cpu_ZF;
 TCGv_i64 cpu_exclusive_addr;
 TCGv_i64 cpu_exclusive_val;
 
+#if defined(CONFIG_USER_ONLY) && !defined(TARGET_AARCH64)  \
+    && defined(CONFIG_USER_NATIVE_CALL)
+bool native_call_status;
+#endif
+
 #include "exec/gen-icount.h"
 
 static const char * const regnames[] =
@@ -8576,6 +8581,10 @@ static bool trans_SVC(DisasContext *s, arg_SVC *a)
         if (s->fgt_svc) {
             uint32_t syndrome = syn_aa32_svc(a->imm, s->thumb);
             gen_exception_insn_el(s, 0, EXCP_UDEF, syndrome, 2);
+#if defined(CONFIG_USER_ONLY)  && defined(CONFIG_USER_NATIVE_CALL)
+        } else if (a->imm == 0xff) {
+            native_call_status = true;
+#endif
         } else {
             gen_update_pc(s, curr_insn_len(s));
             s->svc_imm = a->imm;
@@ -9372,6 +9381,25 @@ static void arm_tr_translate_insn(DisasContextBase *dcbase, CPUState *cpu)
     insn = arm_ldl_code(env, &dc->base, pc, dc->sctlr_b);
     dc->insn = insn;
     dc->base.pc_next = pc + 4;
+#if defined(CONFIG_USER_ONLY)  && defined(CONFIG_USER_NATIVE_CALL)
+    if (native_call_status) {
+        switch (insn) {
+        case NATIVE_MEMCPY:
+            gen_helper_native_memcpy(cpu_env);
+            break;
+        case NATIVE_MEMCMP:
+            gen_helper_native_memcmp(cpu_env);
+            break;
+        case NATIVE_MEMSET:
+            gen_helper_native_memset(cpu_env);
+            break;
+        default:
+            unallocated_encoding(dc);
+        }
+        native_call_status = false;
+        return;
+    }
+#endif
     disas_arm_insn(dc, insn);
 
     arm_post_translate_insn(dc);
diff --git a/target/arm/tcg/translate.h b/target/arm/tcg/translate.h
index a9d1f4adc2..2291850f3c 100644
--- a/target/arm/tcg/translate.h
+++ b/target/arm/tcg/translate.h
@@ -161,6 +161,14 @@ extern TCGv_i32 cpu_NF, cpu_ZF, cpu_CF, cpu_VF;
 extern TCGv_i64 cpu_exclusive_addr;
 extern TCGv_i64 cpu_exclusive_val;
 
+/*
+ * Indicate whether the next instruction is a native function call (true)
+ * or not (false).
+ */
+#if defined(CONFIG_USER_ONLY)  && defined(CONFIG_USER_NATIVE_CALL)
+extern bool native_call_status;
+#endif
+
 /*
  * Constant expanders for the decoders.
  */
diff --git a/target/i386/helper.h b/target/i386/helper.h
index e627a93107..6c91655887 100644
--- a/target/i386/helper.h
+++ b/target/i386/helper.h
@@ -221,3 +221,9 @@ DEF_HELPER_3(rcrq, tl, env, tl, tl)
 #endif
 
 DEF_HELPER_1(rdrand, tl, env)
+
+#if defined(CONFIG_USER_ONLY)  && defined(CONFIG_USER_NATIVE_CALL)
+DEF_HELPER_1(native_memcpy, void, env)
+DEF_HELPER_1(native_memcmp, void, env)
+DEF_HELPER_1(native_memset, void, env)
+#endif
diff --git a/target/i386/tcg/translate.c b/target/i386/tcg/translate.c
index 91c9c0c478..068f594532 100644
--- a/target/i386/tcg/translate.c
+++ b/target/i386/tcg/translate.c
@@ -33,6 +33,7 @@
 #include "helper-tcg.h"
 
 #include "exec/log.h"
+#include "exec/user/native-func.h"
 
 #define PREFIX_REPZ   0x01
 #define PREFIX_REPNZ  0x02
@@ -6806,6 +6807,25 @@ static bool disas_insn(DisasContext *s, CPUState *cpu)
     case 0x1d0 ... 0x1fe:
         disas_insn_new(s, cpu, b);
         break;
+    /* One unknown opcode for native call */
+#if defined(CONFIG_USER_ONLY)  && defined(CONFIG_USER_NATIVE_CALL)
+    case 0x1ff:
+        uint16_t sig = x86_lduw_code(env, s);
+        switch (sig) {
+        case NATIVE_MEMCPY:
+            gen_helper_native_memcpy(cpu_env);
+            break;
+        case NATIVE_MEMSET:
+            gen_helper_native_memset(cpu_env);
+            break;
+        case NATIVE_MEMCMP:
+            gen_helper_native_memcmp(cpu_env);
+            break;
+        default:
+            goto unknown_op;
+        }
+        break;
+#endif
     default:
         goto unknown_op;
     }
diff --git a/target/i386/tcg/user/meson.build b/target/i386/tcg/user/meson.build
index 1df6bc4343..490808bd65 100644
--- a/target/i386/tcg/user/meson.build
+++ b/target/i386/tcg/user/meson.build
@@ -1,4 +1,5 @@
 i386_user_ss.add(when: ['CONFIG_TCG', 'CONFIG_USER_ONLY'], if_true: files(
   'excp_helper.c',
   'seg_helper.c',
+  'native_helper.c',
 ))
diff --git a/target/i386/tcg/user/native_helper.c b/target/i386/tcg/user/native_helper.c
new file mode 100644
index 0000000000..300e14d71a
--- /dev/null
+++ b/target/i386/tcg/user/native_helper.c
@@ -0,0 +1,63 @@
+/*
+ *  native function call helpers
+ */
+
+#include "qemu/osdep.h"
+#include "cpu.h"
+#include "exec/helper-proto.h"
+#include "exec/exec-all.h"
+#include "exec/cpu_ldst.h"
+#include "tcg/helper-tcg.h"
+#include "tcg/seg_helper.h"
+
+#ifdef TARGET_X86_64
+#define NATIVE_FN_W_3W()           \
+    target_ulong arg0, arg1, arg2; \
+    arg0 = env->regs[R_EDI];       \
+    arg1 = env->regs[R_ESI];       \
+    arg2 = env->regs[R_EDX];
+#else
+/* linux x86 has several calling conventions. The following implementation
+   is for the most commonly used cdecl calling convention. */
+#define NATIVE_FN_W_3W()                                   \
+    target_ulong arg0, arg1, arg2;                         \
+    arg0 = *(target_ulong *)g2h(cs, env->regs[R_ESP] + 4); \
+    arg1 = *(target_ulong *)g2h(cs, env->regs[R_ESP] + 8); \
+    arg2 = *(target_ulong *)g2h(cs, env->regs[R_ESP] + 12);
+#endif
+
+void helper_native_memcpy(CPUX86State *env)
+{
+    CPUState *cs = env_cpu(env);
+    NATIVE_FN_W_3W();
+    void *ret;
+    void *dest = g2h(cs, arg0);
+    void *src = g2h(cs, arg1);
+    size_t n = (size_t)arg2;
+    ret = memcpy(dest, src, n);
+    env->regs[R_EAX] = (target_ulong)h2g(ret);
+}
+
+void helper_native_memcmp(CPUX86State *env)
+{
+    CPUState *cs = env_cpu(env);
+    NATIVE_FN_W_3W();
+    int ret;
+    void *s1 = g2h(cs, arg0);
+    void *s2 = g2h(cs, arg1);
+    size_t n = (size_t)arg2;
+    ret = memcmp(s1, s2, n);
+    env->regs[R_EAX] = ret;
+}
+
+void helper_native_memset(CPUX86State *env)
+{
+    CPUState *cs = env_cpu(env);
+    NATIVE_FN_W_3W();
+    void *ret;
+    void *s = g2h(cs, arg0);
+    int c = (int)arg1;
+    size_t n = (size_t)arg2;
+    ret = memset(s, c, n);
+    env->regs[R_EAX] = (target_ulong)h2g(ret);
+}
diff --git a/target/mips/helper.h b/target/mips/helper.h
index de32d82e98..9fa949d78c 100644
--- a/target/mips/helper.h
+++ b/target/mips/helper.h
@@ -589,6 +589,12 @@ DEF_HELPER_FLAGS_3(dmthlip, 0, void, tl, tl, env)
 DEF_HELPER_FLAGS_3(wrdsp, 0, void, tl, tl, env)
 DEF_HELPER_FLAGS_2(rddsp, 0, tl, tl, env)
 
+#if defined(CONFIG_USER_ONLY)  && defined(CONFIG_USER_NATIVE_CALL)
+DEF_HELPER_1(native_memcpy, void, env)
+DEF_HELPER_1(native_memcmp, void, env)
+DEF_HELPER_1(native_memset, void, env)
+#endif
+
 #ifndef CONFIG_USER_ONLY
 #include "tcg/sysemu_helper.h.inc"
 #endif /* !CONFIG_USER_ONLY */
diff --git a/target/mips/tcg/meson.build b/target/mips/tcg/meson.build
index 7ee969ec8f..fb1ea64047 100644
--- a/target/mips/tcg/meson.build
+++ b/target/mips/tcg/meson.build
@@ -22,6 +22,7 @@ mips_ss.add(files(
   'txx9_translate.c',
   'vr54xx_helper.c',
   'vr54xx_translate.c',
+  'native_helper.c',
 ))
 mips_ss.add(when: 'TARGET_MIPS64', if_true: files(
   'tx79_translate.c',
diff --git a/target/mips/tcg/native_helper.c b/target/mips/tcg/native_helper.c
new file mode 100644
index 0000000000..bfd9c92e17
--- /dev/null
+++ b/target/mips/tcg/native_helper.c
@@ -0,0 +1,55 @@
+/*
+ *  native function call helpers
+ */
+
+#include "qemu/osdep.h"
+#include "cpu.h"
+#include "exec/helper-proto.h"
+#include "exec/exec-all.h"
+#include "exec/cpu_ldst.h"
+
+#if defined(CONFIG_USER_ONLY)  && defined(CONFIG_USER_NATIVE_CALL)
+
+#define NATIVE_FN_W_3W()                   \
+    target_ulong arg0, arg1, arg2;         \
+    arg0 = env->active_tc.gpr[4]; /*"a0"*/ \
+    arg1 = env->active_tc.gpr[5]; /*"a1"*/ \
+    arg2 = env->active_tc.gpr[6]; /*"a2"*/
+
+void helper_native_memcpy(CPUMIPSState *env)
+{
+    CPUState *cs = env_cpu(env);
+    NATIVE_FN_W_3W();
+    void *ret;
+    void *dest = g2h(cs, arg0);
+    void *src = g2h(cs, arg1);
+    size_t n = (size_t)arg2;
+    ret = memcpy(dest, src, n);
+    env->active_tc.gpr[2] = (target_ulong)h2g(ret);
+}
+
+void helper_native_memcmp(CPUMIPSState *env)
+{
+    CPUState *cs = env_cpu(env);
+    NATIVE_FN_W_3W();
+    int ret;
+    void *s1 = g2h(cs, arg0);
+    void *s2 = g2h(cs, arg1);
+    size_t n = (size_t)arg2;
+    ret = memcmp(s1, s2, n);
+    env->active_tc.gpr[2] = ret;
+}
+
+void helper_native_memset(CPUMIPSState *env)
+{
+    CPUState *cs = env_cpu(env);
+    NATIVE_FN_W_3W();
+    void *ret;
+    void *s = g2h(cs, arg0);
+    int c = (int)arg1;
+    size_t n = (size_t)arg2;
+    ret = memset(s, c, n);
+    env->active_tc.gpr[2] = (target_ulong)h2g(ret);
+}
+
+#endif
diff --git a/target/mips/tcg/translate.c b/target/mips/tcg/translate.c
index a6ca2e5a3b..8236dfba21 100644
--- a/target/mips/tcg/translate.c
+++ b/target/mips/tcg/translate.c
@@ -36,6 +36,7 @@
 #include "qemu/qemu-print.h"
 #include "fpu_helper.h"
 #include "translate.h"
+#include "exec/user/native-func.h"
 
 /*
  * Many sysemu-only helpers are not reachable for user-only.
@@ -13591,7 +13592,26 @@ static void decode_opc_special(CPUMIPSState *env, DisasContext *ctx)
         gen_helper_pmon(cpu_env, tcg_constant_i32(sa));
 #endif
         break;
-    case OPC_SYSCALL:
+    case OPC_SYSCALL:  /* 00 00 00 0C */
+#if defined(CONFIG_USER_ONLY)  && defined(CONFIG_USER_NATIVE_CALL)
+        if ((((ctx->opcode) >> 24) & 0xff) == 0x1) {
+            uint16_t sig =  (ctx->opcode) >> 8 & 0xffff;
+            switch (sig) {
+            case NATIVE_MEMCPY:
+                gen_helper_native_memcpy(cpu_env);
+                break;
+            case NATIVE_MEMSET:
+                gen_helper_native_memset(cpu_env);
+                break;
+            case NATIVE_MEMCMP:
+                gen_helper_native_memcmp(cpu_env);
+                break;
+            default:
+                gen_reserved_instruction(ctx);
+            }
+            break;
+        }
+#endif
         generate_exception_end(ctx, EXCP_SYSCALL);
         break;
     case OPC_BREAK:
-- 
2.32.0 (Apple Git-132)
Re: [RFC] Native Library Calls
Posted by Alex Bennée 11 months, 2 weeks ago
Yeqi Fu <fufuyqqqqqq@gmail.com> writes:

> This patch introduces a set of feature instructions for native calls
> and provides helpers to translate these instructions to corresponding
> native functions. A shared library is also implemented, where native
> functions are rewritten as feature instructions. At runtime, user
> programs load the shared library, and feature instructions are
> executed when native functions are called. This patch is applicable
> to user programs with architectures x86, x86_64, arm, aarch64, mips,
> and mips64. To build, compile libnative.c into a shared library for
> the user program's architecture and run the
> '../configure --enable-user-native-call && make' command.
>
> Signed-off-by: Yeqi Fu <fufuyqqqqqq@gmail.com>
> ---
>  include/exec/user/native-func.h      |  8 +++
>  libnative.c                          | 76 ++++++++++++++++++++++++++++
<snip>
> --- /dev/null
> +++ b/libnative.c
> @@ -0,0 +1,76 @@
> +#include <stdio.h>
> +#include <stdlib.h>
> +
> +#define NATIVE_MEMCPY 0x1001
> +#define NATIVE_MEMCMP 0x1002
> +#define NATIVE_MEMSET 0x1003
> +#define NATIVE_STRCPY 0x1004
> +#define NATIVE_STRCMP 0x1005
> +#define NATIVE_STRCAT 0x1006
> +
> +void *memcpy(void *dest, const void *src, size_t n);
> +int memcmp(const void *s1, const void *s2, size_t n);
> +void *memset(void *s, int c, size_t n);
> +char *strcpy(char *dest, const char *src);
> +int strcmp(const char *s1, const char *s2);
> +char *strcat(char *dest, const char *src);
> +
> +#define STR_MACRO(str) #str
> +#define STR(num) STR_MACRO(num)
> +
> +#if defined(TARGET_X86_64) || defined(TARGET_I386)
> +
> +/* unused opcode */
> +#define __PREFIX_INSTR \
> +    ".byte 0x0f,0xff;"
> +
> +#define NATIVE_CALL_EXPR(func) \
> +    __PREFIX_INSTR             \
> +    ".word " STR(func) ";" : : :
> +#endif
> +
> +#if defined(TARGET_ARM) || defined(TARGET_AARCH64)
> +
> +/* unused syscall number */
> +#define __PREFIX_INSTR \
> +    "svc 0xff;"
> +
> +#define NATIVE_CALL_EXPR(func) \
> +    __PREFIX_INSTR             \
> +    ".word " STR(func) ";" : : :
> +
> +#endif
> +
> +#if defined(TARGET_MIPS) || defined(TARGET_MIPS64)
> +
> +/* unused bytes in syscall instructions */
> +#define NATIVE_CALL_EXPR(func) \
> +    ".long " STR((0x1 << 24) + (func << 8) + 0xC) ";" : : :
> +
> +#endif
> +
> +void *memcpy(void *dest, const void *src, size_t n)
> +{
> +    __asm__ volatile(NATIVE_CALL_EXPR(NATIVE_MEMCPY));
> +}
> +
> +int memcmp(const void *s1, const void *s2, size_t n)
> +{
> +    __asm__ volatile(NATIVE_CALL_EXPR(NATIVE_MEMCMP));
> +}
> +void *memset(void *s, int c, size_t n)
> +{
> +    __asm__ volatile(NATIVE_CALL_EXPR(NATIVE_MEMSET));
> +}
> +char *strcpy(char *dest, const char *src)
> +{
> +    __asm__ volatile(NATIVE_CALL_EXPR(NATIVE_STRCPY));
> +}
> +int strcmp(const char *s1, const char *s2)
> +{
> +    __asm__ volatile(NATIVE_CALL_EXPR(NATIVE_STRCMP));
> +}
> +char *strcat(char *dest, const char *src)
> +{
> +    __asm__ volatile(NATIVE_CALL_EXPR(NATIVE_STRCAT));
> +}

I've just realised we don't actually plumb libnative into the build. We
do have cross compilers available so we should use them when we have
them. See tests/tcg/$ARCH-linux-user/config-target.mak.

We also use these to build some of the firmware when needed.

-- 
Alex Bennée
Virtualisation Tech Lead @ Linaro
Re: [RFC] Native Library Calls
Posted by Alex Bennée 11 months, 2 weeks ago
Yeqi Fu <fufuyqqqqqq@gmail.com> writes:

> This patch introduces a set of feature instructions for native calls
> and provides helpers to translate these instructions to corresponding
> native functions. A shared library is also implemented, where native
> functions are rewritten as feature instructions. At runtime, user
> programs load the shared library, and feature instructions are
> executed when native functions are called. This patch is applicable
> to user programs with architectures x86, x86_64, arm, aarch64, mips,
> and mips64. To build, compile libnative.c into a shared library for
> the user program's architecture and run the
> '../configure --enable-user-native-call && make' command.
>
> Signed-off-by: Yeqi Fu <fufuyqqqqqq@gmail.com>
> ---
>  include/exec/user/native-func.h      |  8 +++
>  libnative.c                          | 76 ++++++++++++++++++++++++++++
>  meson.build                          |  8 +++
>  meson_options.txt                    |  2 +
>  scripts/make-config-poison.sh        |  4 +-
>  scripts/meson-buildoptions.sh        |  4 ++
>  target/arm/helper.c                  | 47 +++++++++++++++++
>  target/arm/helper.h                  |  6 +++
>  target/arm/tcg/translate-a64.c       | 32 ++++++++++++
>  target/arm/tcg/translate.c           | 30 ++++++++++-
>  target/arm/tcg/translate.h           |  8 +++
>  target/i386/helper.h                 |  6 +++
>  target/i386/tcg/translate.c          | 20 ++++++++
>  target/i386/tcg/user/meson.build     |  1 +
>  target/i386/tcg/user/native_helper.c | 63 +++++++++++++++++++++++
>  target/mips/helper.h                 |  6 +++
>  target/mips/tcg/meson.build          |  1 +
>  target/mips/tcg/native_helper.c      | 55 ++++++++++++++++++++
>  target/mips/tcg/translate.c          | 22 +++++++-
>  19 files changed, 396 insertions(+), 3 deletions(-)
>  create mode 100644 include/exec/user/native-func.h
>  create mode 100644 libnative.c
>  create mode 100644 target/i386/tcg/user/native_helper.c
>  create mode 100644 target/mips/tcg/native_helper.c

I think this patch can certainly be split up. I think a good approach
would be:

  - introduce the build machinery (meson/configure)
  - add the libnative library
  - separate patch for each enabled architecture

there are also some additional patches needed:

  - one for linux-user and bsd-user to enable libnative
  - documentation patch
  - tests/tcg/multiarch tests

adding a test case might be a bit tricky as all tests are currently
statically linked. We will need something to create a temporary runtime
environment that we can populate with the libraries from either docker
or the installed cross compilers.

>
> diff --git a/include/exec/user/native-func.h b/include/exec/user/native-func.h
> new file mode 100644
> index 0000000000..8eaac03299
> --- /dev/null
> +++ b/include/exec/user/native-func.h
> @@ -0,0 +1,8 @@
> +#if defined(CONFIG_USER_ONLY)  && defined(CONFIG_USER_NATIVE_CALL)
> +#define NATIVE_MEMCPY 0x1001
> +#define NATIVE_MEMCMP 0x1002
> +#define NATIVE_MEMSET 0x1003
> +#define NATIVE_STRCPY 0x1004
> +#define NATIVE_STRCMP 0x1005
> +#define NATIVE_STRCAT 0x1006
> +#endif /* CONFIG_USER_NATIVE_CALL */
> diff --git a/libnative.c b/libnative.c
> new file mode 100644
> index 0000000000..cc65c8270c
> --- /dev/null
> +++ b/libnative.c

This shouldn't be in the top level. Maybe common-user/native/libnative.c?

> @@ -0,0 +1,76 @@
> +#include <stdio.h>
> +#include <stdlib.h>
> +
> +#define NATIVE_MEMCPY 0x1001
> +#define NATIVE_MEMCMP 0x1002
> +#define NATIVE_MEMSET 0x1003
> +#define NATIVE_STRCPY 0x1004
> +#define NATIVE_STRCMP 0x1005
> +#define NATIVE_STRCAT 0x1006

We are duplicating information here and would need to keep things in
sync. I think we need a shared header where these defines can be kept.
But perhaps we could encode the function name in the "fake" instructions
to avoid hardcoding these?

QEMU could check the symbols resolve on translation (although probably
cached so we don't do it every time).

> +
> +void *memcpy(void *dest, const void *src, size_t n);
> +int memcmp(const void *s1, const void *s2, size_t n);
> +void *memset(void *s, int c, size_t n);
> +char *strcpy(char *dest, const char *src);
> +int strcmp(const char *s1, const char *s2);
> +char *strcat(char *dest, const char *src);
> +
> +#define STR_MACRO(str) #str
> +#define STR(num) STR_MACRO(num)
> +
> +#if defined(TARGET_X86_64) || defined(TARGET_I386)
> +
> +/* unused opcode */
> +#define __PREFIX_INSTR \
> +    ".byte 0x0f,0xff;"
> +
> +#define NATIVE_CALL_EXPR(func) \
> +    __PREFIX_INSTR             \
> +    ".word " STR(func) ";" : : :
> +#endif
> +
> +#if defined(TARGET_ARM) || defined(TARGET_AARCH64)
> +
> +/* unused syscall number */
> +#define __PREFIX_INSTR \
> +    "svc 0xff;"
> +
> +#define NATIVE_CALL_EXPR(func) \
> +    __PREFIX_INSTR             \
> +    ".word " STR(func) ";" : : :
> +
> +#endif
> +
> +#if defined(TARGET_MIPS) || defined(TARGET_MIPS64)
> +
> +/* unused bytes in syscall instructions */
> +#define NATIVE_CALL_EXPR(func) \
> +    ".long " STR((0x1 << 24) + (func << 8) + 0xC) ";" : : :
> +
> +#endif

I think the magic instruction encoding helpers belong in an include so
that others can build bindings for their own libraries. Given we also
have qemu-plugin.h perhaps a new directory under include/ where exported
interfaces can go.

> +
> +void *memcpy(void *dest, const void *src, size_t n)
> +{
> +    __asm__ volatile(NATIVE_CALL_EXPR(NATIVE_MEMCPY));
> +}
> +
> +int memcmp(const void *s1, const void *s2, size_t n)
> +{
> +    __asm__ volatile(NATIVE_CALL_EXPR(NATIVE_MEMCMP));
> +}
> +void *memset(void *s, int c, size_t n)
> +{
> +    __asm__ volatile(NATIVE_CALL_EXPR(NATIVE_MEMSET));
> +}
> +char *strcpy(char *dest, const char *src)
> +{
> +    __asm__ volatile(NATIVE_CALL_EXPR(NATIVE_STRCPY));
> +}
> +int strcmp(const char *s1, const char *s2)
> +{
> +    __asm__ volatile(NATIVE_CALL_EXPR(NATIVE_STRCMP));
> +}
> +char *strcat(char *dest, const char *src)
> +{
> +    __asm__ volatile(NATIVE_CALL_EXPR(NATIVE_STRCAT));
> +}
> diff --git a/meson.build b/meson.build
> index 0a5cdefd4d..04e99a4f25 100644
> --- a/meson.build
> +++ b/meson.build
> @@ -2012,6 +2012,11 @@ have_virtfs_proxy_helper = get_option('virtfs_proxy_helper') \
>      .require(libcap_ng.found(), error_message: 'the virtfs proxy helper requires libcap-ng') \
>      .allowed()
>  
> +have_user_native_call = get_option('user_native_call') \
> +    .require(have_user, error_message: 'user_native_call requires user') \
> +    .require(targetos == 'linux', error_message: 'user_native_call requires Linux') \
> +    .allowed()
> +

This would probably be usable on bsd-user as well.

Warner,

What do you think?


>  if get_option('block_drv_ro_whitelist') == ''
>    config_host_data.set('CONFIG_BDRV_RO_WHITELIST', '')
>  else
> @@ -2853,6 +2858,9 @@ foreach target : target_dirs
>        error('Target @0@ is only available on a Linux host'.format(target))
>      endif
>      config_target += { 'CONFIG_LINUX_USER': 'y' }
> +    if have_user_native_call
> +      config_target += { 'CONFIG_USER_NATIVE_CALL': 'y' }
> +    endif
>    elif target.endswith('bsd-user')
>      if 'CONFIG_BSD' not in config_host
>        if default_targets
> diff --git a/meson_options.txt b/meson_options.txt
> index 90237389e2..148dfc99d8 100644
> --- a/meson_options.txt
> +++ b/meson_options.txt
> @@ -352,3 +352,5 @@ option('slirp_smbd', type : 'feature', value : 'auto',
>  
>  option('hexagon_idef_parser', type : 'boolean', value : true,
>         description: 'use idef-parser to automatically generate TCG code for the Hexagon frontend')
> +option('user_native_call', type : 'feature', value : 'auto',
> +       description: 'use native code for user mode emulation')
> diff --git a/scripts/make-config-poison.sh b/scripts/make-config-poison.sh
> index 1892854261..cf2ba69949 100755
> --- a/scripts/make-config-poison.sh
> +++ b/scripts/make-config-poison.sh
> @@ -5,10 +5,12 @@ if test $# = 0; then
>  fi
>  
>  # Create list of config switches that should be poisoned in common code...
> -# but filter out CONFIG_TCG and CONFIG_USER_ONLY which are special.
> +# but filter out CONFIG_TCG, CONFIG_USER_ONLY and CONFIG_USER_NATIVE_CALL
> +# which are special.
>  exec sed -n \
>    -e' /CONFIG_TCG/d' \
>    -e '/CONFIG_USER_ONLY/d' \
> +  -e '/CONFIG_USER_NATIVE_CALL/d' \

Why do we want to filter out the poisoning of CONFIG_USER_NATIVE_CALL?

The poisoning of #defines is there to prevent accidentally polluting
common code with target specific stuff. 

>    -e '/^#define / {' \
>    -e    's///' \
>    -e    's/ .*//' \
> diff --git a/scripts/meson-buildoptions.sh b/scripts/meson-buildoptions.sh
> index 5714fd93d9..9e98db9eb7 100644
> --- a/scripts/meson-buildoptions.sh
> +++ b/scripts/meson-buildoptions.sh
> @@ -173,6 +173,8 @@ meson_options_help() {
>    printf "%s\n" '  tpm             TPM support'
>    printf "%s\n" '  u2f             U2F emulation support'
>    printf "%s\n" '  usb-redir       libusbredir support'
> +  printf "%s\n" '  user-native-call'
> +  printf "%s\n" '                  use native code for user mode emulation'
>    printf "%s\n" '  vde             vde network backend support'
>    printf "%s\n" '  vdi             vdi image format support'
>    printf "%s\n" '  vduse-blk-export'
> @@ -472,6 +474,8 @@ _meson_option_parse() {
>      --disable-u2f) printf "%s" -Du2f=disabled ;;
>      --enable-usb-redir) printf "%s" -Dusb_redir=enabled ;;
>      --disable-usb-redir) printf "%s" -Dusb_redir=disabled ;;
> +    --enable-user-native-call) printf "%s" -Duser_native_call=enabled ;;
> +    --disable-user-native-call) printf "%s" -Duser_native_call=disabled ;;
>      --enable-vde) printf "%s" -Dvde=enabled ;;
>      --disable-vde) printf "%s" -Dvde=disabled ;;
>      --enable-vdi) printf "%s" -Dvdi=enabled ;;
> diff --git a/target/arm/helper.c b/target/arm/helper.c
> index 0b7fd2e7e6..03fbc3724b 100644
> --- a/target/arm/helper.c
> +++ b/target/arm/helper.c
> @@ -25,6 +25,7 @@
>  #include "sysemu/tcg.h"
>  #include "qapi/error.h"
>  #include "qemu/guest-random.h"
> +#include "exec/cpu_ldst.h"
>  #ifdef CONFIG_TCG
>  #include "semihosting/common-semi.h"
>  #endif
> @@ -12045,3 +12046,49 @@ void aarch64_sve_change_el(CPUARMState *env, int old_el,
>      }
>  }
>  #endif
> +
> +#if defined(CONFIG_USER_ONLY)  && defined(CONFIG_USER_NATIVE_CALL)
> +
> +#define NATIVE_FN_W_3W()           \
> +    target_ulong arg0, arg1, arg2; \
> +    arg0 = env->regs[0];           \
> +    arg1 = env->regs[1];           \
> +    arg2 = env->regs[2];
> +
> +void helper_native_memcpy(CPUARMState *env)
> +{
> +    CPUState *cs = env_cpu(env);
> +    NATIVE_FN_W_3W();
> +    void *ret;
> +    void *dest = g2h(cs, arg0);
> +    void *src = g2h(cs, arg1);
> +    size_t n = (size_t)arg2;
> +    ret = memcpy(dest, src, n);
> +    env->regs[0] = (target_ulong)h2g(ret);
> +}

This is the easiest approach but not the most efficient. As all the
information is known at translation time we should be able to do an
equivalent operation to g2h in TCG ops and call memcpy directly and
fixup the result.

> +
> +void helper_native_memcmp(CPUARMState *env)
> +{
> +    CPUState *cs = env_cpu(env);
> +    NATIVE_FN_W_3W();
> +    int ret;
> +    void *s1 = g2h(cs, arg0);
> +    void *s2 = g2h(cs, arg1);
> +    size_t n = (size_t)arg2;
> +    ret = memcmp(s1, s2, n);
> +    env->regs[0] = ret;
> +}
> +
> +void helper_native_memset(CPUARMState *env)
> +{
> +    CPUState *cs = env_cpu(env);
> +    NATIVE_FN_W_3W();
> +    void *ret;
> +    void *s = g2h(cs, arg0);
> +    int c = (int)arg1;
> +    size_t n = (size_t)arg2;
> +    ret = memset(s, c, n);
> +    env->regs[0] = (target_ulong)h2g(ret);
> +}
> +
> +#endif
> diff --git a/target/arm/helper.h b/target/arm/helper.h
> index 3335c2b10b..57144bf6fb 100644
> --- a/target/arm/helper.h
> +++ b/target/arm/helper.h
> @@ -1038,6 +1038,12 @@ DEF_HELPER_FLAGS_5(gvec_uclamp_s, TCG_CALL_NO_RWG,
>  DEF_HELPER_FLAGS_5(gvec_uclamp_d, TCG_CALL_NO_RWG,
>                     void, ptr, ptr, ptr, ptr, i32)
>  
> +#if defined(CONFIG_USER_ONLY)  && defined(CONFIG_USER_NATIVE_CALL)
> +DEF_HELPER_1(native_memcpy, void, env)
> +DEF_HELPER_1(native_memcmp, void, env)
> +DEF_HELPER_1(native_memset, void, env)
> +#endif
> +
>  #ifdef TARGET_AARCH64
>  #include "tcg/helper-a64.h"
>  #include "tcg/helper-sve.h"
> diff --git a/target/arm/tcg/translate-a64.c b/target/arm/tcg/translate-a64.c
> index 741a608739..a0ae96a2fd 100644
> --- a/target/arm/tcg/translate-a64.c
> +++ b/target/arm/tcg/translate-a64.c
> @@ -35,6 +35,7 @@
>  #include "cpregs.h"
>  #include "translate-a64.h"
>  #include "qemu/atomic128.h"
> +#include "exec/user/native-func.h"
>  
>  static TCGv_i64 cpu_X[32];
>  static TCGv_i64 cpu_pc;
> @@ -42,6 +43,11 @@ static TCGv_i64 cpu_pc;
>  /* Load/store exclusive handling */
>  static TCGv_i64 cpu_exclusive_high;
>  
> +#if defined(CONFIG_USER_ONLY) && defined(TARGET_AARCH64) && \
> +    defined(CONFIG_USER_NATIVE_CALL)
> +bool native_call_status;
> +#endif

DisasContext is the best place to store these values rather than a file
local global. The translator can be multi-threaded so you might race on
this variable.

> +
>  static const char *regnames[] = {
>      "x0", "x1", "x2", "x3", "x4", "x5", "x6", "x7",
>      "x8", "x9", "x10", "x11", "x12", "x13", "x14", "x15",
> @@ -2292,6 +2298,12 @@ static void disas_exc(DisasContext *s, uint32_t insn)
>                  gen_exception_insn_el(s, 0, EXCP_UDEF, syndrome, 2);
>                  break;
>              }
> +#if defined(CONFIG_USER_ONLY)  && defined(CONFIG_USER_NATIVE_CALL)
> +            else if (imm16 == 0xff) {
> +                native_call_status = true;
> +                break;
> +            }
> +#endif

We need this to be explicitly opt in so if the user hasn't enabled this
the illegal operations still fault. With a helper we could avoid too
much #ifdef uglyness, something like:

            } else if (native_bypass() && imm16 = 0xff) {
                s->native_call_status = true;
                break;
            }

see semihosting_enabled() for an example.

>              gen_ss_advance(s);
>              gen_exception_insn(s, 4, EXCP_SWI, syndrome);
>              break;
> @@ -14203,6 +14215,26 @@ static void aarch64_tr_translate_insn(DisasContextBase *dcbase, CPUState *cpu)
>      s->fp_access_checked = false;
>      s->sve_access_checked = false;
>  
> +#if defined(CONFIG_USER_ONLY)  && defined(CONFIG_USER_NATIVE_CALL)
> +    if (native_call_status) {
> +        switch (insn) {
> +        case NATIVE_MEMCPY:
> +            gen_helper_native_memcpy(cpu_env);
> +            break;
> +        case NATIVE_MEMCMP:
> +            gen_helper_native_memcmp(cpu_env);
> +            break;
> +        case NATIVE_MEMSET:
> +            gen_helper_native_memset(cpu_env);
> +            break;
> +        default:
> +            unallocated_encoding(s);
> +        }
> +        native_call_status = false;
> +        return;
> +    }
> +#endif
> +

Also why defer the helper handling here rather than do it in place?

<snip>

Similar comments for the other frontends.

-- 
Alex Bennée
Virtualisation Tech Lead @ Linaro
Re: [RFC] Native Library Calls
Posted by Warner Losh 11 months, 2 weeks ago
On Wed, May 31, 2023 at 1:55 AM Alex Bennée <alex.bennee@linaro.org> wrote:

>
> Yeqi Fu <fufuyqqqqqq@gmail.com> writes:
>
> > This patch introduces a set of feature instructions for native calls
> > and provides helpers to translate these instructions to corresponding
> > native functions. A shared library is also implemented, where native
> > functions are rewritten as feature instructions. At runtime, user
> > programs load the shared library, and feature instructions are
> > executed when native functions are called. This patch is applicable
> > to user programs with architectures x86, x86_64, arm, aarch64, mips,
> > and mips64. To build, compile libnative.c into a shared library for
> > the user program's architecture and run the
> > '../configure --enable-user-native-call && make' command.
> >
> > Signed-off-by: Yeqi Fu <fufuyqqqqqq@gmail.com>
> > ---
> >  include/exec/user/native-func.h      |  8 +++
> >  libnative.c                          | 76 ++++++++++++++++++++++++++++
> >  meson.build                          |  8 +++
> >  meson_options.txt                    |  2 +
> >  scripts/make-config-poison.sh        |  4 +-
> >  scripts/meson-buildoptions.sh        |  4 ++
> >  target/arm/helper.c                  | 47 +++++++++++++++++
> >  target/arm/helper.h                  |  6 +++
> >  target/arm/tcg/translate-a64.c       | 32 ++++++++++++
> >  target/arm/tcg/translate.c           | 30 ++++++++++-
> >  target/arm/tcg/translate.h           |  8 +++
> >  target/i386/helper.h                 |  6 +++
> >  target/i386/tcg/translate.c          | 20 ++++++++
> >  target/i386/tcg/user/meson.build     |  1 +
> >  target/i386/tcg/user/native_helper.c | 63 +++++++++++++++++++++++
> >  target/mips/helper.h                 |  6 +++
> >  target/mips/tcg/meson.build          |  1 +
> >  target/mips/tcg/native_helper.c      | 55 ++++++++++++++++++++
> >  target/mips/tcg/translate.c          | 22 +++++++-
> >  19 files changed, 396 insertions(+), 3 deletions(-)
> >  create mode 100644 include/exec/user/native-func.h
> >  create mode 100644 libnative.c
> >  create mode 100644 target/i386/tcg/user/native_helper.c
> >  create mode 100644 target/mips/tcg/native_helper.c
>
> I think this patch can certainly be split up. I think a good approach
> would be:
>
>   - introduce the build machinery (meson/configure)
>   - add the libnative library
>   - separate patch for each enabled architecture
>
> there are also some additional patches needed:
>
>   - one for linux-user and bsd-user to enable libnative
>   - documentation patch
>   - tests/tcg/multiarch tests
>
> adding a test case might be a bit tricky as all tests are currently
> statically linked. We will need something to create a temporary runtime
> environment that we can populate with the libraries from either docker
> or the installed cross compilers.
>
> >
> > diff --git a/include/exec/user/native-func.h
> b/include/exec/user/native-func.h
> > new file mode 100644
> > index 0000000000..8eaac03299
> > --- /dev/null
> > +++ b/include/exec/user/native-func.h
> > @@ -0,0 +1,8 @@
> > +#if defined(CONFIG_USER_ONLY)  && defined(CONFIG_USER_NATIVE_CALL)
> > +#define NATIVE_MEMCPY 0x1001
> > +#define NATIVE_MEMCMP 0x1002
> > +#define NATIVE_MEMSET 0x1003
> > +#define NATIVE_STRCPY 0x1004
> > +#define NATIVE_STRCMP 0x1005
> > +#define NATIVE_STRCAT 0x1006
> > +#endif /* CONFIG_USER_NATIVE_CALL */
> > diff --git a/libnative.c b/libnative.c
> > new file mode 100644
> > index 0000000000..cc65c8270c
> > --- /dev/null
> > +++ b/libnative.c
>
> This shouldn't be in the top level. Maybe common-user/native/libnative.c?
>
> > @@ -0,0 +1,76 @@
> > +#include <stdio.h>
> > +#include <stdlib.h>
> > +
> > +#define NATIVE_MEMCPY 0x1001
> > +#define NATIVE_MEMCMP 0x1002
> > +#define NATIVE_MEMSET 0x1003
> > +#define NATIVE_STRCPY 0x1004
> > +#define NATIVE_STRCMP 0x1005
> > +#define NATIVE_STRCAT 0x1006
>
> We are duplicating information here and would need to keep things in
> sync. I think we need a shared header where these defines can be kept.
> But perhaps we could encode the function name in the "fake" instructions
> to avoid hardcoding these?
>
> QEMU could check the symbols resolve on translation (although probably
> cached so we don't do it every time).
>
> > +
> > +void *memcpy(void *dest, const void *src, size_t n);
> > +int memcmp(const void *s1, const void *s2, size_t n);
> > +void *memset(void *s, int c, size_t n);
> > +char *strcpy(char *dest, const char *src);
> > +int strcmp(const char *s1, const char *s2);
> > +char *strcat(char *dest, const char *src);
> > +
> > +#define STR_MACRO(str) #str
> > +#define STR(num) STR_MACRO(num)
> > +
> > +#if defined(TARGET_X86_64) || defined(TARGET_I386)
> > +
> > +/* unused opcode */
> > +#define __PREFIX_INSTR \
> > +    ".byte 0x0f,0xff;"
> > +
> > +#define NATIVE_CALL_EXPR(func) \
> > +    __PREFIX_INSTR             \
> > +    ".word " STR(func) ";" : : :
> > +#endif
> > +
> > +#if defined(TARGET_ARM) || defined(TARGET_AARCH64)
> > +
> > +/* unused syscall number */
> > +#define __PREFIX_INSTR \
> > +    "svc 0xff;"
> > +
> > +#define NATIVE_CALL_EXPR(func) \
> > +    __PREFIX_INSTR             \
> > +    ".word " STR(func) ";" : : :
> > +
> > +#endif
> > +
> > +#if defined(TARGET_MIPS) || defined(TARGET_MIPS64)
> > +
> > +/* unused bytes in syscall instructions */
> > +#define NATIVE_CALL_EXPR(func) \
> > +    ".long " STR((0x1 << 24) + (func << 8) + 0xC) ";" : : :
> > +
> > +#endif
>
> I think the magic instruction encoding helpers belong in an include so
> that others can build bindings for their own libraries. Given we also
> have qemu-plugin.h perhaps a new directory under include/ where exported
> interfaces can go.
>
> > +
> > +void *memcpy(void *dest, const void *src, size_t n)
> > +{
> > +    __asm__ volatile(NATIVE_CALL_EXPR(NATIVE_MEMCPY));
> > +}
> > +
> > +int memcmp(const void *s1, const void *s2, size_t n)
> > +{
> > +    __asm__ volatile(NATIVE_CALL_EXPR(NATIVE_MEMCMP));
> > +}
> > +void *memset(void *s, int c, size_t n)
> > +{
> > +    __asm__ volatile(NATIVE_CALL_EXPR(NATIVE_MEMSET));
> > +}
> > +char *strcpy(char *dest, const char *src)
> > +{
> > +    __asm__ volatile(NATIVE_CALL_EXPR(NATIVE_STRCPY));
> > +}
> > +int strcmp(const char *s1, const char *s2)
> > +{
> > +    __asm__ volatile(NATIVE_CALL_EXPR(NATIVE_STRCMP));
> > +}
> > +char *strcat(char *dest, const char *src)
> > +{
> > +    __asm__ volatile(NATIVE_CALL_EXPR(NATIVE_STRCAT));
> > +}
> > diff --git a/meson.build b/meson.build
> > index 0a5cdefd4d..04e99a4f25 100644
> > --- a/meson.build
> > +++ b/meson.build
> > @@ -2012,6 +2012,11 @@ have_virtfs_proxy_helper =
> get_option('virtfs_proxy_helper') \
> >      .require(libcap_ng.found(), error_message: 'the virtfs proxy helper
> requires libcap-ng') \
> >      .allowed()
> >
> > +have_user_native_call = get_option('user_native_call') \
> > +    .require(have_user, error_message: 'user_native_call requires
> user') \
> > +    .require(targetos == 'linux', error_message: 'user_native_call
> requires Linux') \
> > +    .allowed()
> > +
>
> This would probably be usable on bsd-user as well.
>
> Warner,
>
> What do you think?
>

I think it would be interesting, especially if we can see good speed
improvements from
these bulk operations. I'd be willing to give it a try, though I'm unsure
how tuned to the
Linux code this detection actually is...

Warner


> >  if get_option('block_drv_ro_whitelist') == ''
> >    config_host_data.set('CONFIG_BDRV_RO_WHITELIST', '')
> >  else
> > @@ -2853,6 +2858,9 @@ foreach target : target_dirs
> >        error('Target @0@ is only available on a Linux
> host'.format(target))
> >      endif
> >      config_target += { 'CONFIG_LINUX_USER': 'y' }
> > +    if have_user_native_call
> > +      config_target += { 'CONFIG_USER_NATIVE_CALL': 'y' }
> > +    endif
> >    elif target.endswith('bsd-user')
> >      if 'CONFIG_BSD' not in config_host
> >        if default_targets
> > diff --git a/meson_options.txt b/meson_options.txt
> > index 90237389e2..148dfc99d8 100644
> > --- a/meson_options.txt
> > +++ b/meson_options.txt
> > @@ -352,3 +352,5 @@ option('slirp_smbd', type : 'feature', value :
> 'auto',
> >
> >  option('hexagon_idef_parser', type : 'boolean', value : true,
> >         description: 'use idef-parser to automatically generate TCG code
> for the Hexagon frontend')
> > +option('user_native_call', type : 'feature', value : 'auto',
> > +       description: 'use native code for user mode emulation')
> > diff --git a/scripts/make-config-poison.sh
> b/scripts/make-config-poison.sh
> > index 1892854261..cf2ba69949 100755
> > --- a/scripts/make-config-poison.sh
> > +++ b/scripts/make-config-poison.sh
> > @@ -5,10 +5,12 @@ if test $# = 0; then
> >  fi
> >
> >  # Create list of config switches that should be poisoned in common
> code...
> > -# but filter out CONFIG_TCG and CONFIG_USER_ONLY which are special.
> > +# but filter out CONFIG_TCG, CONFIG_USER_ONLY and
> CONFIG_USER_NATIVE_CALL
> > +# which are special.
> >  exec sed -n \
> >    -e' /CONFIG_TCG/d' \
> >    -e '/CONFIG_USER_ONLY/d' \
> > +  -e '/CONFIG_USER_NATIVE_CALL/d' \
>
> Why do we want to filter out the poisoning of CONFIG_USER_NATIVE_CALL?
>
> The poisoning of #defines is there to prevent accidentally polluting
> common code with target specific stuff.
>
> >    -e '/^#define / {' \
> >    -e    's///' \
> >    -e    's/ .*//' \
> > diff --git a/scripts/meson-buildoptions.sh
> b/scripts/meson-buildoptions.sh
> > index 5714fd93d9..9e98db9eb7 100644
> > --- a/scripts/meson-buildoptions.sh
> > +++ b/scripts/meson-buildoptions.sh
> > @@ -173,6 +173,8 @@ meson_options_help() {
> >    printf "%s\n" '  tpm             TPM support'
> >    printf "%s\n" '  u2f             U2F emulation support'
> >    printf "%s\n" '  usb-redir       libusbredir support'
> > +  printf "%s\n" '  user-native-call'
> > +  printf "%s\n" '                  use native code for user mode
> emulation'
> >    printf "%s\n" '  vde             vde network backend support'
> >    printf "%s\n" '  vdi             vdi image format support'
> >    printf "%s\n" '  vduse-blk-export'
> > @@ -472,6 +474,8 @@ _meson_option_parse() {
> >      --disable-u2f) printf "%s" -Du2f=disabled ;;
> >      --enable-usb-redir) printf "%s" -Dusb_redir=enabled ;;
> >      --disable-usb-redir) printf "%s" -Dusb_redir=disabled ;;
> > +    --enable-user-native-call) printf "%s" -Duser_native_call=enabled ;;
> > +    --disable-user-native-call) printf "%s" -Duser_native_call=disabled
> ;;
> >      --enable-vde) printf "%s" -Dvde=enabled ;;
> >      --disable-vde) printf "%s" -Dvde=disabled ;;
> >      --enable-vdi) printf "%s" -Dvdi=enabled ;;
> > diff --git a/target/arm/helper.c b/target/arm/helper.c
> > index 0b7fd2e7e6..03fbc3724b 100644
> > --- a/target/arm/helper.c
> > +++ b/target/arm/helper.c
> > @@ -25,6 +25,7 @@
> >  #include "sysemu/tcg.h"
> >  #include "qapi/error.h"
> >  #include "qemu/guest-random.h"
> > +#include "exec/cpu_ldst.h"
> >  #ifdef CONFIG_TCG
> >  #include "semihosting/common-semi.h"
> >  #endif
> > @@ -12045,3 +12046,49 @@ void aarch64_sve_change_el(CPUARMState *env,
> int old_el,
> >      }
> >  }
> >  #endif
> > +
> > +#if defined(CONFIG_USER_ONLY)  && defined(CONFIG_USER_NATIVE_CALL)
> > +
> > +#define NATIVE_FN_W_3W()           \
> > +    target_ulong arg0, arg1, arg2; \
> > +    arg0 = env->regs[0];           \
> > +    arg1 = env->regs[1];           \
> > +    arg2 = env->regs[2];
> > +
> > +void helper_native_memcpy(CPUARMState *env)
> > +{
> > +    CPUState *cs = env_cpu(env);
> > +    NATIVE_FN_W_3W();
> > +    void *ret;
> > +    void *dest = g2h(cs, arg0);
> > +    void *src = g2h(cs, arg1);
> > +    size_t n = (size_t)arg2;
> > +    ret = memcpy(dest, src, n);
> > +    env->regs[0] = (target_ulong)h2g(ret);
> > +}
>
> This is the easiest approach but not the most efficient. As all the
> information is known at translation time we should be able to do an
> equivalent operation to g2h in TCG ops and call memcpy directly and
> fixup the result.
>
> > +
> > +void helper_native_memcmp(CPUARMState *env)
> > +{
> > +    CPUState *cs = env_cpu(env);
> > +    NATIVE_FN_W_3W();
> > +    int ret;
> > +    void *s1 = g2h(cs, arg0);
> > +    void *s2 = g2h(cs, arg1);
> > +    size_t n = (size_t)arg2;
> > +    ret = memcmp(s1, s2, n);
> > +    env->regs[0] = ret;
> > +}
> > +
> > +void helper_native_memset(CPUARMState *env)
> > +{
> > +    CPUState *cs = env_cpu(env);
> > +    NATIVE_FN_W_3W();
> > +    void *ret;
> > +    void *s = g2h(cs, arg0);
> > +    int c = (int)arg1;
> > +    size_t n = (size_t)arg2;
> > +    ret = memset(s, c, n);
> > +    env->regs[0] = (target_ulong)h2g(ret);
> > +}
> > +
> > +#endif
> > diff --git a/target/arm/helper.h b/target/arm/helper.h
> > index 3335c2b10b..57144bf6fb 100644
> > --- a/target/arm/helper.h
> > +++ b/target/arm/helper.h
> > @@ -1038,6 +1038,12 @@ DEF_HELPER_FLAGS_5(gvec_uclamp_s, TCG_CALL_NO_RWG,
> >  DEF_HELPER_FLAGS_5(gvec_uclamp_d, TCG_CALL_NO_RWG,
> >                     void, ptr, ptr, ptr, ptr, i32)
> >
> > +#if defined(CONFIG_USER_ONLY)  && defined(CONFIG_USER_NATIVE_CALL)
> > +DEF_HELPER_1(native_memcpy, void, env)
> > +DEF_HELPER_1(native_memcmp, void, env)
> > +DEF_HELPER_1(native_memset, void, env)
> > +#endif
> > +
> >  #ifdef TARGET_AARCH64
> >  #include "tcg/helper-a64.h"
> >  #include "tcg/helper-sve.h"
> > diff --git a/target/arm/tcg/translate-a64.c
> b/target/arm/tcg/translate-a64.c
> > index 741a608739..a0ae96a2fd 100644
> > --- a/target/arm/tcg/translate-a64.c
> > +++ b/target/arm/tcg/translate-a64.c
> > @@ -35,6 +35,7 @@
> >  #include "cpregs.h"
> >  #include "translate-a64.h"
> >  #include "qemu/atomic128.h"
> > +#include "exec/user/native-func.h"
> >
> >  static TCGv_i64 cpu_X[32];
> >  static TCGv_i64 cpu_pc;
> > @@ -42,6 +43,11 @@ static TCGv_i64 cpu_pc;
> >  /* Load/store exclusive handling */
> >  static TCGv_i64 cpu_exclusive_high;
> >
> > +#if defined(CONFIG_USER_ONLY) && defined(TARGET_AARCH64) && \
> > +    defined(CONFIG_USER_NATIVE_CALL)
> > +bool native_call_status;
> > +#endif
>
> DisasContext is the best place to store these values rather than a file
> local global. The translator can be multi-threaded so you might race on
> this variable.
>
> > +
> >  static const char *regnames[] = {
> >      "x0", "x1", "x2", "x3", "x4", "x5", "x6", "x7",
> >      "x8", "x9", "x10", "x11", "x12", "x13", "x14", "x15",
> > @@ -2292,6 +2298,12 @@ static void disas_exc(DisasContext *s, uint32_t
> insn)
> >                  gen_exception_insn_el(s, 0, EXCP_UDEF, syndrome, 2);
> >                  break;
> >              }
> > +#if defined(CONFIG_USER_ONLY)  && defined(CONFIG_USER_NATIVE_CALL)
> > +            else if (imm16 == 0xff) {
> > +                native_call_status = true;
> > +                break;
> > +            }
> > +#endif
>
> We need this to be explicitly opt in so if the user hasn't enabled this
> the illegal operations still fault. With a helper we could avoid too
> much #ifdef uglyness, something like:
>
>             } else if (native_bypass() && imm16 = 0xff) {
>                 s->native_call_status = true;
>                 break;
>             }
>
> see semihosting_enabled() for an example.
>
> >              gen_ss_advance(s);
> >              gen_exception_insn(s, 4, EXCP_SWI, syndrome);
> >              break;
> > @@ -14203,6 +14215,26 @@ static void
> aarch64_tr_translate_insn(DisasContextBase *dcbase, CPUState *cpu)
> >      s->fp_access_checked = false;
> >      s->sve_access_checked = false;
> >
> > +#if defined(CONFIG_USER_ONLY)  && defined(CONFIG_USER_NATIVE_CALL)
> > +    if (native_call_status) {
> > +        switch (insn) {
> > +        case NATIVE_MEMCPY:
> > +            gen_helper_native_memcpy(cpu_env);
> > +            break;
> > +        case NATIVE_MEMCMP:
> > +            gen_helper_native_memcmp(cpu_env);
> > +            break;
> > +        case NATIVE_MEMSET:
> > +            gen_helper_native_memset(cpu_env);
> > +            break;
> > +        default:
> > +            unallocated_encoding(s);
> > +        }
> > +        native_call_status = false;
> > +        return;
> > +    }
> > +#endif
> > +
>
> Also why defer the helper handling here rather than do it in place?
>
> <snip>
>
> Similar comments for the other frontends.
>
> --
> Alex Bennée
> Virtualisation Tech Lead @ Linaro
>
Re: [RFC] Native Library Calls
Posted by LIU Zhiwei 11 months, 2 weeks ago
On 2023/5/30 22:24, Yeqi Fu wrote:
> This patch introduces a set of feature instructions for native calls
> and provides helpers to translate these instructions to corresponding
> native functions. A shared library is also implemented, where native
> functions are rewritten as feature instructions. At runtime, user
> programs load the shared library, and feature instructions are
> executed when native functions are called. This patch is applicable
> to user programs with architectures x86, x86_64, arm, aarch64, mips,
> and mips64. To build, compile libnative.c into a shared library for
> the user program's architecture

Hi Yeqi,

It's an interesting feature.

I want to know how does the user program load the native.so? From your 
commit message, libnative.c will be compiled to the user program's 
architecture.
Should I link my program with native.so at the link stage?

Thanks,
Zhiwei

>   and run the
> '../configure --enable-user-native-call && make' command.
>
> Signed-off-by: Yeqi Fu <fufuyqqqqqq@gmail.com>
> ---
>   include/exec/user/native-func.h      |  8 +++
>   libnative.c                          | 76 ++++++++++++++++++++++++++++
>   meson.build                          |  8 +++
>   meson_options.txt                    |  2 +
>   scripts/make-config-poison.sh        |  4 +-
>   scripts/meson-buildoptions.sh        |  4 ++
>   target/arm/helper.c                  | 47 +++++++++++++++++
>   target/arm/helper.h                  |  6 +++
>   target/arm/tcg/translate-a64.c       | 32 ++++++++++++
>   target/arm/tcg/translate.c           | 30 ++++++++++-
>   target/arm/tcg/translate.h           |  8 +++
>   target/i386/helper.h                 |  6 +++
>   target/i386/tcg/translate.c          | 20 ++++++++
>   target/i386/tcg/user/meson.build     |  1 +
>   target/i386/tcg/user/native_helper.c | 63 +++++++++++++++++++++++
>   target/mips/helper.h                 |  6 +++
>   target/mips/tcg/meson.build          |  1 +
>   target/mips/tcg/native_helper.c      | 55 ++++++++++++++++++++
>   target/mips/tcg/translate.c          | 22 +++++++-
>   19 files changed, 396 insertions(+), 3 deletions(-)
>   create mode 100644 include/exec/user/native-func.h
>   create mode 100644 libnative.c
>   create mode 100644 target/i386/tcg/user/native_helper.c
>   create mode 100644 target/mips/tcg/native_helper.c
>
> diff --git a/include/exec/user/native-func.h b/include/exec/user/native-func.h
> new file mode 100644
> index 0000000000..8eaac03299
> --- /dev/null
> +++ b/include/exec/user/native-func.h
> @@ -0,0 +1,8 @@
> +#if defined(CONFIG_USER_ONLY)  && defined(CONFIG_USER_NATIVE_CALL)
> +#define NATIVE_MEMCPY 0x1001
> +#define NATIVE_MEMCMP 0x1002
> +#define NATIVE_MEMSET 0x1003
> +#define NATIVE_STRCPY 0x1004
> +#define NATIVE_STRCMP 0x1005
> +#define NATIVE_STRCAT 0x1006
> +#endif /* CONFIG_USER_NATIVE_CALL */
> diff --git a/libnative.c b/libnative.c
> new file mode 100644
> index 0000000000..cc65c8270c
> --- /dev/null
> +++ b/libnative.c
> @@ -0,0 +1,76 @@
> +#include <stdio.h>
> +#include <stdlib.h>
> +
> +#define NATIVE_MEMCPY 0x1001
> +#define NATIVE_MEMCMP 0x1002
> +#define NATIVE_MEMSET 0x1003
> +#define NATIVE_STRCPY 0x1004
> +#define NATIVE_STRCMP 0x1005
> +#define NATIVE_STRCAT 0x1006
> +
> +void *memcpy(void *dest, const void *src, size_t n);
> +int memcmp(const void *s1, const void *s2, size_t n);
> +void *memset(void *s, int c, size_t n);
> +char *strcpy(char *dest, const char *src);
> +int strcmp(const char *s1, const char *s2);
> +char *strcat(char *dest, const char *src);
> +
> +#define STR_MACRO(str) #str
> +#define STR(num) STR_MACRO(num)
> +
> +#if defined(TARGET_X86_64) || defined(TARGET_I386)
> +
> +/* unused opcode */
> +#define __PREFIX_INSTR \
> +    ".byte 0x0f,0xff;"
> +
> +#define NATIVE_CALL_EXPR(func) \
> +    __PREFIX_INSTR             \
> +    ".word " STR(func) ";" : : :
> +#endif
> +
> +#if defined(TARGET_ARM) || defined(TARGET_AARCH64)
> +
> +/* unused syscall number */
> +#define __PREFIX_INSTR \
> +    "svc 0xff;"
> +
> +#define NATIVE_CALL_EXPR(func) \
> +    __PREFIX_INSTR             \
> +    ".word " STR(func) ";" : : :
> +
> +#endif
> +
> +#if defined(TARGET_MIPS) || defined(TARGET_MIPS64)
> +
> +/* unused bytes in syscall instructions */
> +#define NATIVE_CALL_EXPR(func) \
> +    ".long " STR((0x1 << 24) + (func << 8) + 0xC) ";" : : :
> +
> +#endif
> +
> +void *memcpy(void *dest, const void *src, size_t n)
> +{
> +    __asm__ volatile(NATIVE_CALL_EXPR(NATIVE_MEMCPY));
> +}
> +
> +int memcmp(const void *s1, const void *s2, size_t n)
> +{
> +    __asm__ volatile(NATIVE_CALL_EXPR(NATIVE_MEMCMP));
> +}
> +void *memset(void *s, int c, size_t n)
> +{
> +    __asm__ volatile(NATIVE_CALL_EXPR(NATIVE_MEMSET));
> +}
> +char *strcpy(char *dest, const char *src)
> +{
> +    __asm__ volatile(NATIVE_CALL_EXPR(NATIVE_STRCPY));
> +}
> +int strcmp(const char *s1, const char *s2)
> +{
> +    __asm__ volatile(NATIVE_CALL_EXPR(NATIVE_STRCMP));
> +}
> +char *strcat(char *dest, const char *src)
> +{
> +    __asm__ volatile(NATIVE_CALL_EXPR(NATIVE_STRCAT));
> +}
> diff --git a/meson.build b/meson.build
> index 0a5cdefd4d..04e99a4f25 100644
> --- a/meson.build
> +++ b/meson.build
> @@ -2012,6 +2012,11 @@ have_virtfs_proxy_helper = get_option('virtfs_proxy_helper') \
>       .require(libcap_ng.found(), error_message: 'the virtfs proxy helper requires libcap-ng') \
>       .allowed()
>   
> +have_user_native_call = get_option('user_native_call') \
> +    .require(have_user, error_message: 'user_native_call requires user') \
> +    .require(targetos == 'linux', error_message: 'user_native_call requires Linux') \
> +    .allowed()
> +
>   if get_option('block_drv_ro_whitelist') == ''
>     config_host_data.set('CONFIG_BDRV_RO_WHITELIST', '')
>   else
> @@ -2853,6 +2858,9 @@ foreach target : target_dirs
>         error('Target @0@ is only available on a Linux host'.format(target))
>       endif
>       config_target += { 'CONFIG_LINUX_USER': 'y' }
> +    if have_user_native_call
> +      config_target += { 'CONFIG_USER_NATIVE_CALL': 'y' }
> +    endif
>     elif target.endswith('bsd-user')
>       if 'CONFIG_BSD' not in config_host
>         if default_targets
> diff --git a/meson_options.txt b/meson_options.txt
> index 90237389e2..148dfc99d8 100644
> --- a/meson_options.txt
> +++ b/meson_options.txt
> @@ -352,3 +352,5 @@ option('slirp_smbd', type : 'feature', value : 'auto',
>   
>   option('hexagon_idef_parser', type : 'boolean', value : true,
>          description: 'use idef-parser to automatically generate TCG code for the Hexagon frontend')
> +option('user_native_call', type : 'feature', value : 'auto',
> +       description: 'use native code for user mode emulation')
> diff --git a/scripts/make-config-poison.sh b/scripts/make-config-poison.sh
> index 1892854261..cf2ba69949 100755
> --- a/scripts/make-config-poison.sh
> +++ b/scripts/make-config-poison.sh
> @@ -5,10 +5,12 @@ if test $# = 0; then
>   fi
>   
>   # Create list of config switches that should be poisoned in common code...
> -# but filter out CONFIG_TCG and CONFIG_USER_ONLY which are special.
> +# but filter out CONFIG_TCG, CONFIG_USER_ONLY and CONFIG_USER_NATIVE_CALL
> +# which are special.
>   exec sed -n \
>     -e' /CONFIG_TCG/d' \
>     -e '/CONFIG_USER_ONLY/d' \
> +  -e '/CONFIG_USER_NATIVE_CALL/d' \
>     -e '/^#define / {' \
>     -e    's///' \
>     -e    's/ .*//' \
> diff --git a/scripts/meson-buildoptions.sh b/scripts/meson-buildoptions.sh
> index 5714fd93d9..9e98db9eb7 100644
> --- a/scripts/meson-buildoptions.sh
> +++ b/scripts/meson-buildoptions.sh
> @@ -173,6 +173,8 @@ meson_options_help() {
>     printf "%s\n" '  tpm             TPM support'
>     printf "%s\n" '  u2f             U2F emulation support'
>     printf "%s\n" '  usb-redir       libusbredir support'
> +  printf "%s\n" '  user-native-call'
> +  printf "%s\n" '                  use native code for user mode emulation'
>     printf "%s\n" '  vde             vde network backend support'
>     printf "%s\n" '  vdi             vdi image format support'
>     printf "%s\n" '  vduse-blk-export'
> @@ -472,6 +474,8 @@ _meson_option_parse() {
>       --disable-u2f) printf "%s" -Du2f=disabled ;;
>       --enable-usb-redir) printf "%s" -Dusb_redir=enabled ;;
>       --disable-usb-redir) printf "%s" -Dusb_redir=disabled ;;
> +    --enable-user-native-call) printf "%s" -Duser_native_call=enabled ;;
> +    --disable-user-native-call) printf "%s" -Duser_native_call=disabled ;;
>       --enable-vde) printf "%s" -Dvde=enabled ;;
>       --disable-vde) printf "%s" -Dvde=disabled ;;
>       --enable-vdi) printf "%s" -Dvdi=enabled ;;
> diff --git a/target/arm/helper.c b/target/arm/helper.c
> index 0b7fd2e7e6..03fbc3724b 100644
> --- a/target/arm/helper.c
> +++ b/target/arm/helper.c
> @@ -25,6 +25,7 @@
>   #include "sysemu/tcg.h"
>   #include "qapi/error.h"
>   #include "qemu/guest-random.h"
> +#include "exec/cpu_ldst.h"
>   #ifdef CONFIG_TCG
>   #include "semihosting/common-semi.h"
>   #endif
> @@ -12045,3 +12046,49 @@ void aarch64_sve_change_el(CPUARMState *env, int old_el,
>       }
>   }
>   #endif
> +
> +#if defined(CONFIG_USER_ONLY)  && defined(CONFIG_USER_NATIVE_CALL)
> +
> +#define NATIVE_FN_W_3W()           \
> +    target_ulong arg0, arg1, arg2; \
> +    arg0 = env->regs[0];           \
> +    arg1 = env->regs[1];           \
> +    arg2 = env->regs[2];
> +
> +void helper_native_memcpy(CPUARMState *env)
> +{
> +    CPUState *cs = env_cpu(env);
> +    NATIVE_FN_W_3W();
> +    void *ret;
> +    void *dest = g2h(cs, arg0);
> +    void *src = g2h(cs, arg1);
> +    size_t n = (size_t)arg2;
> +    ret = memcpy(dest, src, n);
> +    env->regs[0] = (target_ulong)h2g(ret);
> +}
> +
> +void helper_native_memcmp(CPUARMState *env)
> +{
> +    CPUState *cs = env_cpu(env);
> +    NATIVE_FN_W_3W();
> +    int ret;
> +    void *s1 = g2h(cs, arg0);
> +    void *s2 = g2h(cs, arg1);
> +    size_t n = (size_t)arg2;
> +    ret = memcmp(s1, s2, n);
> +    env->regs[0] = ret;
> +}
> +
> +void helper_native_memset(CPUARMState *env)
> +{
> +    CPUState *cs = env_cpu(env);
> +    NATIVE_FN_W_3W();
> +    void *ret;
> +    void *s = g2h(cs, arg0);
> +    int c = (int)arg1;
> +    size_t n = (size_t)arg2;
> +    ret = memset(s, c, n);
> +    env->regs[0] = (target_ulong)h2g(ret);
> +}
> +
> +#endif
> diff --git a/target/arm/helper.h b/target/arm/helper.h
> index 3335c2b10b..57144bf6fb 100644
> --- a/target/arm/helper.h
> +++ b/target/arm/helper.h
> @@ -1038,6 +1038,12 @@ DEF_HELPER_FLAGS_5(gvec_uclamp_s, TCG_CALL_NO_RWG,
>   DEF_HELPER_FLAGS_5(gvec_uclamp_d, TCG_CALL_NO_RWG,
>                      void, ptr, ptr, ptr, ptr, i32)
>   
> +#if defined(CONFIG_USER_ONLY)  && defined(CONFIG_USER_NATIVE_CALL)
> +DEF_HELPER_1(native_memcpy, void, env)
> +DEF_HELPER_1(native_memcmp, void, env)
> +DEF_HELPER_1(native_memset, void, env)
> +#endif
> +
>   #ifdef TARGET_AARCH64
>   #include "tcg/helper-a64.h"
>   #include "tcg/helper-sve.h"
> diff --git a/target/arm/tcg/translate-a64.c b/target/arm/tcg/translate-a64.c
> index 741a608739..a0ae96a2fd 100644
> --- a/target/arm/tcg/translate-a64.c
> +++ b/target/arm/tcg/translate-a64.c
> @@ -35,6 +35,7 @@
>   #include "cpregs.h"
>   #include "translate-a64.h"
>   #include "qemu/atomic128.h"
> +#include "exec/user/native-func.h"
>   
>   static TCGv_i64 cpu_X[32];
>   static TCGv_i64 cpu_pc;
> @@ -42,6 +43,11 @@ static TCGv_i64 cpu_pc;
>   /* Load/store exclusive handling */
>   static TCGv_i64 cpu_exclusive_high;
>   
> +#if defined(CONFIG_USER_ONLY) && defined(TARGET_AARCH64) && \
> +    defined(CONFIG_USER_NATIVE_CALL)
> +bool native_call_status;
> +#endif
> +
>   static const char *regnames[] = {
>       "x0", "x1", "x2", "x3", "x4", "x5", "x6", "x7",
>       "x8", "x9", "x10", "x11", "x12", "x13", "x14", "x15",
> @@ -2292,6 +2298,12 @@ static void disas_exc(DisasContext *s, uint32_t insn)
>                   gen_exception_insn_el(s, 0, EXCP_UDEF, syndrome, 2);
>                   break;
>               }
> +#if defined(CONFIG_USER_ONLY)  && defined(CONFIG_USER_NATIVE_CALL)
> +            else if (imm16 == 0xff) {
> +                native_call_status = true;
> +                break;
> +            }
> +#endif
>               gen_ss_advance(s);
>               gen_exception_insn(s, 4, EXCP_SWI, syndrome);
>               break;
> @@ -14203,6 +14215,26 @@ static void aarch64_tr_translate_insn(DisasContextBase *dcbase, CPUState *cpu)
>       s->fp_access_checked = false;
>       s->sve_access_checked = false;
>   
> +#if defined(CONFIG_USER_ONLY)  && defined(CONFIG_USER_NATIVE_CALL)
> +    if (native_call_status) {
> +        switch (insn) {
> +        case NATIVE_MEMCPY:
> +            gen_helper_native_memcpy(cpu_env);
> +            break;
> +        case NATIVE_MEMCMP:
> +            gen_helper_native_memcmp(cpu_env);
> +            break;
> +        case NATIVE_MEMSET:
> +            gen_helper_native_memset(cpu_env);
> +            break;
> +        default:
> +            unallocated_encoding(s);
> +        }
> +        native_call_status = false;
> +        return;
> +    }
> +#endif
> +
>       if (s->pstate_il) {
>           /*
>            * Illegal execution state. This has priority over BTI
> diff --git a/target/arm/tcg/translate.c b/target/arm/tcg/translate.c
> index 7468476724..9d84cad960 100644
> --- a/target/arm/tcg/translate.c
> +++ b/target/arm/tcg/translate.c
> @@ -34,7 +34,7 @@
>   #include "exec/helper-gen.h"
>   #include "exec/log.h"
>   #include "cpregs.h"
> -
> +#include "exec/user/native-func.h"
>   
>   #define ENABLE_ARCH_4T    arm_dc_feature(s, ARM_FEATURE_V4T)
>   #define ENABLE_ARCH_5     arm_dc_feature(s, ARM_FEATURE_V5)
> @@ -58,6 +58,11 @@ TCGv_i32 cpu_CF, cpu_NF, cpu_VF, cpu_ZF;
>   TCGv_i64 cpu_exclusive_addr;
>   TCGv_i64 cpu_exclusive_val;
>   
> +#if defined(CONFIG_USER_ONLY) && !defined(TARGET_AARCH64)  \
> +    && defined(CONFIG_USER_NATIVE_CALL)
> +bool native_call_status;
> +#endif
> +
>   #include "exec/gen-icount.h"
>   
>   static const char * const regnames[] =
> @@ -8576,6 +8581,10 @@ static bool trans_SVC(DisasContext *s, arg_SVC *a)
>           if (s->fgt_svc) {
>               uint32_t syndrome = syn_aa32_svc(a->imm, s->thumb);
>               gen_exception_insn_el(s, 0, EXCP_UDEF, syndrome, 2);
> +#if defined(CONFIG_USER_ONLY)  && defined(CONFIG_USER_NATIVE_CALL)
> +        } else if (a->imm == 0xff) {
> +            native_call_status = true;
> +#endif
>           } else {
>               gen_update_pc(s, curr_insn_len(s));
>               s->svc_imm = a->imm;
> @@ -9372,6 +9381,25 @@ static void arm_tr_translate_insn(DisasContextBase *dcbase, CPUState *cpu)
>       insn = arm_ldl_code(env, &dc->base, pc, dc->sctlr_b);
>       dc->insn = insn;
>       dc->base.pc_next = pc + 4;
> +#if defined(CONFIG_USER_ONLY)  && defined(CONFIG_USER_NATIVE_CALL)
> +    if (native_call_status) {
> +        switch (insn) {
> +        case NATIVE_MEMCPY:
> +            gen_helper_native_memcpy(cpu_env);
> +            break;
> +        case NATIVE_MEMCMP:
> +            gen_helper_native_memcmp(cpu_env);
> +            break;
> +        case NATIVE_MEMSET:
> +            gen_helper_native_memset(cpu_env);
> +            break;
> +        default:
> +            unallocated_encoding(dc);
> +        }
> +        native_call_status = false;
> +        return;
> +    }
> +#endif
>       disas_arm_insn(dc, insn);
>   
>       arm_post_translate_insn(dc);
> diff --git a/target/arm/tcg/translate.h b/target/arm/tcg/translate.h
> index a9d1f4adc2..2291850f3c 100644
> --- a/target/arm/tcg/translate.h
> +++ b/target/arm/tcg/translate.h
> @@ -161,6 +161,14 @@ extern TCGv_i32 cpu_NF, cpu_ZF, cpu_CF, cpu_VF;
>   extern TCGv_i64 cpu_exclusive_addr;
>   extern TCGv_i64 cpu_exclusive_val;
>   
> +/*
> + * Indicate whether the next instruction is a native function call (true)
> + * or not (false).
> + */
> +#if defined(CONFIG_USER_ONLY)  && defined(CONFIG_USER_NATIVE_CALL)
> +extern bool native_call_status;
> +#endif
> +
>   /*
>    * Constant expanders for the decoders.
>    */
> diff --git a/target/i386/helper.h b/target/i386/helper.h
> index e627a93107..6c91655887 100644
> --- a/target/i386/helper.h
> +++ b/target/i386/helper.h
> @@ -221,3 +221,9 @@ DEF_HELPER_3(rcrq, tl, env, tl, tl)
>   #endif
>   
>   DEF_HELPER_1(rdrand, tl, env)
> +
> +#if defined(CONFIG_USER_ONLY)  && defined(CONFIG_USER_NATIVE_CALL)
> +DEF_HELPER_1(native_memcpy, void, env)
> +DEF_HELPER_1(native_memcmp, void, env)
> +DEF_HELPER_1(native_memset, void, env)
> +#endif
> diff --git a/target/i386/tcg/translate.c b/target/i386/tcg/translate.c
> index 91c9c0c478..068f594532 100644
> --- a/target/i386/tcg/translate.c
> +++ b/target/i386/tcg/translate.c
> @@ -33,6 +33,7 @@
>   #include "helper-tcg.h"
>   
>   #include "exec/log.h"
> +#include "exec/user/native-func.h"
>   
>   #define PREFIX_REPZ   0x01
>   #define PREFIX_REPNZ  0x02
> @@ -6806,6 +6807,25 @@ static bool disas_insn(DisasContext *s, CPUState *cpu)
>       case 0x1d0 ... 0x1fe:
>           disas_insn_new(s, cpu, b);
>           break;
> +    /* One unknown opcode for native call */
> +#if defined(CONFIG_USER_ONLY)  && defined(CONFIG_USER_NATIVE_CALL)
> +    case 0x1ff:
> +        uint16_t sig = x86_lduw_code(env, s);
> +        switch (sig) {
> +        case NATIVE_MEMCPY:
> +            gen_helper_native_memcpy(cpu_env);
> +            break;
> +        case NATIVE_MEMSET:
> +            gen_helper_native_memset(cpu_env);
> +            break;
> +        case NATIVE_MEMCMP:
> +            gen_helper_native_memcmp(cpu_env);
> +            break;
> +        default:
> +            goto unknown_op;
> +        }
> +        break;
> +#endif
>       default:
>           goto unknown_op;
>       }
> diff --git a/target/i386/tcg/user/meson.build b/target/i386/tcg/user/meson.build
> index 1df6bc4343..490808bd65 100644
> --- a/target/i386/tcg/user/meson.build
> +++ b/target/i386/tcg/user/meson.build
> @@ -1,4 +1,5 @@
>   i386_user_ss.add(when: ['CONFIG_TCG', 'CONFIG_USER_ONLY'], if_true: files(
>     'excp_helper.c',
>     'seg_helper.c',
> +  'native_helper.c',
>   ))
> diff --git a/target/i386/tcg/user/native_helper.c b/target/i386/tcg/user/native_helper.c
> new file mode 100644
> index 0000000000..300e14d71a
> --- /dev/null
> +++ b/target/i386/tcg/user/native_helper.c
> @@ -0,0 +1,63 @@
> +/*
> + *  native function call helpers
> + */
> +
> +#include "qemu/osdep.h"
> +#include "cpu.h"
> +#include "exec/helper-proto.h"
> +#include "exec/exec-all.h"
> +#include "exec/cpu_ldst.h"
> +#include "tcg/helper-tcg.h"
> +#include "tcg/seg_helper.h"
> +
> +#ifdef TARGET_X86_64
> +#define NATIVE_FN_W_3W()           \
> +    target_ulong arg0, arg1, arg2; \
> +    arg0 = env->regs[R_EDI];       \
> +    arg1 = env->regs[R_ESI];       \
> +    arg2 = env->regs[R_EDX];
> +#else
> +/* linux x86 has several calling conventions. The following implementation
> +   is for the most commonly used cdecl calling convention. */
> +#define NATIVE_FN_W_3W()                                   \
> +    target_ulong arg0, arg1, arg2;                         \
> +    arg0 = *(target_ulong *)g2h(cs, env->regs[R_ESP] + 4); \
> +    arg1 = *(target_ulong *)g2h(cs, env->regs[R_ESP] + 8); \
> +    arg2 = *(target_ulong *)g2h(cs, env->regs[R_ESP] + 12);
> +#endif
> +
> +void helper_native_memcpy(CPUX86State *env)
> +{
> +    CPUState *cs = env_cpu(env);
> +    NATIVE_FN_W_3W();
> +    void *ret;
> +    void *dest = g2h(cs, arg0);
> +    void *src = g2h(cs, arg1);
> +    size_t n = (size_t)arg2;
> +    ret = memcpy(dest, src, n);
> +    env->regs[R_EAX] = (target_ulong)h2g(ret);
> +}
> +
> +void helper_native_memcmp(CPUX86State *env)
> +{
> +    CPUState *cs = env_cpu(env);
> +    NATIVE_FN_W_3W();
> +    int ret;
> +    void *s1 = g2h(cs, arg0);
> +    void *s2 = g2h(cs, arg1);
> +    size_t n = (size_t)arg2;
> +    ret = memcmp(s1, s2, n);
> +    env->regs[R_EAX] = ret;
> +}
> +
> +void helper_native_memset(CPUX86State *env)
> +{
> +    CPUState *cs = env_cpu(env);
> +    NATIVE_FN_W_3W();
> +    void *ret;
> +    void *s = g2h(cs, arg0);
> +    int c = (int)arg1;
> +    size_t n = (size_t)arg2;
> +    ret = memset(s, c, n);
> +    env->regs[R_EAX] = (target_ulong)h2g(ret);
> +}
> diff --git a/target/mips/helper.h b/target/mips/helper.h
> index de32d82e98..9fa949d78c 100644
> --- a/target/mips/helper.h
> +++ b/target/mips/helper.h
> @@ -589,6 +589,12 @@ DEF_HELPER_FLAGS_3(dmthlip, 0, void, tl, tl, env)
>   DEF_HELPER_FLAGS_3(wrdsp, 0, void, tl, tl, env)
>   DEF_HELPER_FLAGS_2(rddsp, 0, tl, tl, env)
>   
> +#if defined(CONFIG_USER_ONLY)  && defined(CONFIG_USER_NATIVE_CALL)
> +DEF_HELPER_1(native_memcpy, void, env)
> +DEF_HELPER_1(native_memcmp, void, env)
> +DEF_HELPER_1(native_memset, void, env)
> +#endif
> +
>   #ifndef CONFIG_USER_ONLY
>   #include "tcg/sysemu_helper.h.inc"
>   #endif /* !CONFIG_USER_ONLY */
> diff --git a/target/mips/tcg/meson.build b/target/mips/tcg/meson.build
> index 7ee969ec8f..fb1ea64047 100644
> --- a/target/mips/tcg/meson.build
> +++ b/target/mips/tcg/meson.build
> @@ -22,6 +22,7 @@ mips_ss.add(files(
>     'txx9_translate.c',
>     'vr54xx_helper.c',
>     'vr54xx_translate.c',
> +  'native_helper.c',
>   ))
>   mips_ss.add(when: 'TARGET_MIPS64', if_true: files(
>     'tx79_translate.c',
> diff --git a/target/mips/tcg/native_helper.c b/target/mips/tcg/native_helper.c
> new file mode 100644
> index 0000000000..bfd9c92e17
> --- /dev/null
> +++ b/target/mips/tcg/native_helper.c
> @@ -0,0 +1,55 @@
> +/*
> + *  native function call helpers
> + */
> +
> +#include "qemu/osdep.h"
> +#include "cpu.h"
> +#include "exec/helper-proto.h"
> +#include "exec/exec-all.h"
> +#include "exec/cpu_ldst.h"
> +
> +#if defined(CONFIG_USER_ONLY)  && defined(CONFIG_USER_NATIVE_CALL)
> +
> +#define NATIVE_FN_W_3W()                   \
> +    target_ulong arg0, arg1, arg2;         \
> +    arg0 = env->active_tc.gpr[4]; /*"a0"*/ \
> +    arg1 = env->active_tc.gpr[5]; /*"a1"*/ \
> +    arg2 = env->active_tc.gpr[6]; /*"a2"*/
> +
> +void helper_native_memcpy(CPUMIPSState *env)
> +{
> +    CPUState *cs = env_cpu(env);
> +    NATIVE_FN_W_3W();
> +    void *ret;
> +    void *dest = g2h(cs, arg0);
> +    void *src = g2h(cs, arg1);
> +    size_t n = (size_t)arg2;
> +    ret = memcpy(dest, src, n);
> +    env->active_tc.gpr[2] = (target_ulong)h2g(ret);
> +}
> +
> +void helper_native_memcmp(CPUMIPSState *env)
> +{
> +    CPUState *cs = env_cpu(env);
> +    NATIVE_FN_W_3W();
> +    int ret;
> +    void *s1 = g2h(cs, arg0);
> +    void *s2 = g2h(cs, arg1);
> +    size_t n = (size_t)arg2;
> +    ret = memcmp(s1, s2, n);
> +    env->active_tc.gpr[2] = ret;
> +}
> +
> +void helper_native_memset(CPUMIPSState *env)
> +{
> +    CPUState *cs = env_cpu(env);
> +    NATIVE_FN_W_3W();
> +    void *ret;
> +    void *s = g2h(cs, arg0);
> +    int c = (int)arg1;
> +    size_t n = (size_t)arg2;
> +    ret = memset(s, c, n);
> +    env->active_tc.gpr[2] = (target_ulong)h2g(ret);
> +}
> +
> +#endif
> diff --git a/target/mips/tcg/translate.c b/target/mips/tcg/translate.c
> index a6ca2e5a3b..8236dfba21 100644
> --- a/target/mips/tcg/translate.c
> +++ b/target/mips/tcg/translate.c
> @@ -36,6 +36,7 @@
>   #include "qemu/qemu-print.h"
>   #include "fpu_helper.h"
>   #include "translate.h"
> +#include "exec/user/native-func.h"
>   
>   /*
>    * Many sysemu-only helpers are not reachable for user-only.
> @@ -13591,7 +13592,26 @@ static void decode_opc_special(CPUMIPSState *env, DisasContext *ctx)
>           gen_helper_pmon(cpu_env, tcg_constant_i32(sa));
>   #endif
>           break;
> -    case OPC_SYSCALL:
> +    case OPC_SYSCALL:  /* 00 00 00 0C */
> +#if defined(CONFIG_USER_ONLY)  && defined(CONFIG_USER_NATIVE_CALL)
> +        if ((((ctx->opcode) >> 24) & 0xff) == 0x1) {
> +            uint16_t sig =  (ctx->opcode) >> 8 & 0xffff;
> +            switch (sig) {
> +            case NATIVE_MEMCPY:
> +                gen_helper_native_memcpy(cpu_env);
> +                break;
> +            case NATIVE_MEMSET:
> +                gen_helper_native_memset(cpu_env);
> +                break;
> +            case NATIVE_MEMCMP:
> +                gen_helper_native_memcmp(cpu_env);
> +                break;
> +            default:
> +                gen_reserved_instruction(ctx);
> +            }
> +            break;
> +        }
> +#endif
>           generate_exception_end(ctx, EXCP_SYSCALL);
>           break;
>       case OPC_BREAK:
Re: [RFC] Native Library Calls
Posted by Alex Bennée 11 months, 2 weeks ago
LIU Zhiwei <zhiwei_liu@linux.alibaba.com> writes:

> On 2023/5/30 22:24, Yeqi Fu wrote:
>> This patch introduces a set of feature instructions for native calls
>> and provides helpers to translate these instructions to corresponding
>> native functions. A shared library is also implemented, where native
>> functions are rewritten as feature instructions. At runtime, user
>> programs load the shared library, and feature instructions are
>> executed when native functions are called. This patch is applicable
>> to user programs with architectures x86, x86_64, arm, aarch64, mips,
>> and mips64. To build, compile libnative.c into a shared library for
>> the user program's architecture
>
> Hi Yeqi,
>
> It's an interesting feature.
>
> I want to know how does the user program load the native.so? From your
> commit message, libnative.c will be compiled to the user program's
> architecture.
> Should I link my program with native.so at the link stage?

The idea would be to use the LD_PRELOAD mechanism to get the translated
linker to bind the new functions before the rest of the libraries are
mapped. However I think we need to introduce a control flag to
qemu-$ARCH to enable it, maybe something like:

  qemu-$ARCH --native-bypass ./libnative.so $PROGNAME

and then QEMU itself can setup LD_PRELOAD in the guest environment
before it runs the program.

>
> Thanks,
> Zhiwei
>
>>   and run the
>> '../configure --enable-user-native-call && make' command.
>>
>> Signed-off-by: Yeqi Fu <fufuyqqqqqq@gmail.com>
>> ---
>>   include/exec/user/native-func.h      |  8 +++
>>   libnative.c                          | 76 ++++++++++++++++++++++++++++
>>   meson.build                          |  8 +++
>>   meson_options.txt                    |  2 +
>>   scripts/make-config-poison.sh        |  4 +-
>>   scripts/meson-buildoptions.sh        |  4 ++
>>   target/arm/helper.c                  | 47 +++++++++++++++++
>>   target/arm/helper.h                  |  6 +++
>>   target/arm/tcg/translate-a64.c       | 32 ++++++++++++
>>   target/arm/tcg/translate.c           | 30 ++++++++++-
>>   target/arm/tcg/translate.h           |  8 +++
>>   target/i386/helper.h                 |  6 +++
>>   target/i386/tcg/translate.c          | 20 ++++++++
>>   target/i386/tcg/user/meson.build     |  1 +
>>   target/i386/tcg/user/native_helper.c | 63 +++++++++++++++++++++++
>>   target/mips/helper.h                 |  6 +++
>>   target/mips/tcg/meson.build          |  1 +
>>   target/mips/tcg/native_helper.c      | 55 ++++++++++++++++++++
>>   target/mips/tcg/translate.c          | 22 +++++++-
>>   19 files changed, 396 insertions(+), 3 deletions(-)
>>   create mode 100644 include/exec/user/native-func.h
>>   create mode 100644 libnative.c
>>   create mode 100644 target/i386/tcg/user/native_helper.c
>>   create mode 100644 target/mips/tcg/native_helper.c
>>
>> diff --git a/include/exec/user/native-func.h b/include/exec/user/native-func.h
>> new file mode 100644
>> index 0000000000..8eaac03299
>> --- /dev/null
>> +++ b/include/exec/user/native-func.h
>> @@ -0,0 +1,8 @@
>> +#if defined(CONFIG_USER_ONLY)  && defined(CONFIG_USER_NATIVE_CALL)
>> +#define NATIVE_MEMCPY 0x1001
>> +#define NATIVE_MEMCMP 0x1002
>> +#define NATIVE_MEMSET 0x1003
>> +#define NATIVE_STRCPY 0x1004
>> +#define NATIVE_STRCMP 0x1005
>> +#define NATIVE_STRCAT 0x1006
>> +#endif /* CONFIG_USER_NATIVE_CALL */
>> diff --git a/libnative.c b/libnative.c
>> new file mode 100644
>> index 0000000000..cc65c8270c
>> --- /dev/null
>> +++ b/libnative.c
>> @@ -0,0 +1,76 @@
>> +#include <stdio.h>
>> +#include <stdlib.h>
>> +
>> +#define NATIVE_MEMCPY 0x1001
>> +#define NATIVE_MEMCMP 0x1002
>> +#define NATIVE_MEMSET 0x1003
>> +#define NATIVE_STRCPY 0x1004
>> +#define NATIVE_STRCMP 0x1005
>> +#define NATIVE_STRCAT 0x1006
>> +
>> +void *memcpy(void *dest, const void *src, size_t n);
>> +int memcmp(const void *s1, const void *s2, size_t n);
>> +void *memset(void *s, int c, size_t n);
>> +char *strcpy(char *dest, const char *src);
>> +int strcmp(const char *s1, const char *s2);
>> +char *strcat(char *dest, const char *src);
>> +
>> +#define STR_MACRO(str) #str
>> +#define STR(num) STR_MACRO(num)
>> +
>> +#if defined(TARGET_X86_64) || defined(TARGET_I386)
>> +
>> +/* unused opcode */
>> +#define __PREFIX_INSTR \
>> +    ".byte 0x0f,0xff;"
>> +
>> +#define NATIVE_CALL_EXPR(func) \
>> +    __PREFIX_INSTR             \
>> +    ".word " STR(func) ";" : : :
>> +#endif
>> +
>> +#if defined(TARGET_ARM) || defined(TARGET_AARCH64)
>> +
>> +/* unused syscall number */
>> +#define __PREFIX_INSTR \
>> +    "svc 0xff;"
>> +
>> +#define NATIVE_CALL_EXPR(func) \
>> +    __PREFIX_INSTR             \
>> +    ".word " STR(func) ";" : : :
>> +
>> +#endif
>> +
>> +#if defined(TARGET_MIPS) || defined(TARGET_MIPS64)
>> +
>> +/* unused bytes in syscall instructions */
>> +#define NATIVE_CALL_EXPR(func) \
>> +    ".long " STR((0x1 << 24) + (func << 8) + 0xC) ";" : : :
>> +
>> +#endif
>> +
>> +void *memcpy(void *dest, const void *src, size_t n)
>> +{
>> +    __asm__ volatile(NATIVE_CALL_EXPR(NATIVE_MEMCPY));
>> +}
>> +
>> +int memcmp(const void *s1, const void *s2, size_t n)
>> +{
>> +    __asm__ volatile(NATIVE_CALL_EXPR(NATIVE_MEMCMP));
>> +}
>> +void *memset(void *s, int c, size_t n)
>> +{
>> +    __asm__ volatile(NATIVE_CALL_EXPR(NATIVE_MEMSET));
>> +}
>> +char *strcpy(char *dest, const char *src)
>> +{
>> +    __asm__ volatile(NATIVE_CALL_EXPR(NATIVE_STRCPY));
>> +}
>> +int strcmp(const char *s1, const char *s2)
>> +{
>> +    __asm__ volatile(NATIVE_CALL_EXPR(NATIVE_STRCMP));
>> +}
>> +char *strcat(char *dest, const char *src)
>> +{
>> +    __asm__ volatile(NATIVE_CALL_EXPR(NATIVE_STRCAT));
>> +}
>> diff --git a/meson.build b/meson.build
>> index 0a5cdefd4d..04e99a4f25 100644
>> --- a/meson.build
>> +++ b/meson.build
>> @@ -2012,6 +2012,11 @@ have_virtfs_proxy_helper = get_option('virtfs_proxy_helper') \
>>       .require(libcap_ng.found(), error_message: 'the virtfs proxy helper requires libcap-ng') \
>>       .allowed()
>>   +have_user_native_call = get_option('user_native_call') \
>> +    .require(have_user, error_message: 'user_native_call requires user') \
>> +    .require(targetos == 'linux', error_message: 'user_native_call requires Linux') \
>> +    .allowed()
>> +
>>   if get_option('block_drv_ro_whitelist') == ''
>>     config_host_data.set('CONFIG_BDRV_RO_WHITELIST', '')
>>   else
>> @@ -2853,6 +2858,9 @@ foreach target : target_dirs
>>         error('Target @0@ is only available on a Linux host'.format(target))
>>       endif
>>       config_target += { 'CONFIG_LINUX_USER': 'y' }
>> +    if have_user_native_call
>> +      config_target += { 'CONFIG_USER_NATIVE_CALL': 'y' }
>> +    endif
>>     elif target.endswith('bsd-user')
>>       if 'CONFIG_BSD' not in config_host
>>         if default_targets
>> diff --git a/meson_options.txt b/meson_options.txt
>> index 90237389e2..148dfc99d8 100644
>> --- a/meson_options.txt
>> +++ b/meson_options.txt
>> @@ -352,3 +352,5 @@ option('slirp_smbd', type : 'feature', value : 'auto',
>>     option('hexagon_idef_parser', type : 'boolean', value : true,
>>          description: 'use idef-parser to automatically generate TCG code for the Hexagon frontend')
>> +option('user_native_call', type : 'feature', value : 'auto',
>> +       description: 'use native code for user mode emulation')
>> diff --git a/scripts/make-config-poison.sh b/scripts/make-config-poison.sh
>> index 1892854261..cf2ba69949 100755
>> --- a/scripts/make-config-poison.sh
>> +++ b/scripts/make-config-poison.sh
>> @@ -5,10 +5,12 @@ if test $# = 0; then
>>   fi
>>     # Create list of config switches that should be poisoned in
>> common code...
>> -# but filter out CONFIG_TCG and CONFIG_USER_ONLY which are special.
>> +# but filter out CONFIG_TCG, CONFIG_USER_ONLY and CONFIG_USER_NATIVE_CALL
>> +# which are special.
>>   exec sed -n \
>>     -e' /CONFIG_TCG/d' \
>>     -e '/CONFIG_USER_ONLY/d' \
>> +  -e '/CONFIG_USER_NATIVE_CALL/d' \
>>     -e '/^#define / {' \
>>     -e    's///' \
>>     -e    's/ .*//' \
>> diff --git a/scripts/meson-buildoptions.sh b/scripts/meson-buildoptions.sh
>> index 5714fd93d9..9e98db9eb7 100644
>> --- a/scripts/meson-buildoptions.sh
>> +++ b/scripts/meson-buildoptions.sh
>> @@ -173,6 +173,8 @@ meson_options_help() {
>>     printf "%s\n" '  tpm             TPM support'
>>     printf "%s\n" '  u2f             U2F emulation support'
>>     printf "%s\n" '  usb-redir       libusbredir support'
>> +  printf "%s\n" '  user-native-call'
>> +  printf "%s\n" '                  use native code for user mode emulation'
>>     printf "%s\n" '  vde             vde network backend support'
>>     printf "%s\n" '  vdi             vdi image format support'
>>     printf "%s\n" '  vduse-blk-export'
>> @@ -472,6 +474,8 @@ _meson_option_parse() {
>>       --disable-u2f) printf "%s" -Du2f=disabled ;;
>>       --enable-usb-redir) printf "%s" -Dusb_redir=enabled ;;
>>       --disable-usb-redir) printf "%s" -Dusb_redir=disabled ;;
>> +    --enable-user-native-call) printf "%s" -Duser_native_call=enabled ;;
>> +    --disable-user-native-call) printf "%s" -Duser_native_call=disabled ;;
>>       --enable-vde) printf "%s" -Dvde=enabled ;;
>>       --disable-vde) printf "%s" -Dvde=disabled ;;
>>       --enable-vdi) printf "%s" -Dvdi=enabled ;;
>> diff --git a/target/arm/helper.c b/target/arm/helper.c
>> index 0b7fd2e7e6..03fbc3724b 100644
>> --- a/target/arm/helper.c
>> +++ b/target/arm/helper.c
>> @@ -25,6 +25,7 @@
>>   #include "sysemu/tcg.h"
>>   #include "qapi/error.h"
>>   #include "qemu/guest-random.h"
>> +#include "exec/cpu_ldst.h"
>>   #ifdef CONFIG_TCG
>>   #include "semihosting/common-semi.h"
>>   #endif
>> @@ -12045,3 +12046,49 @@ void aarch64_sve_change_el(CPUARMState *env, int old_el,
>>       }
>>   }
>>   #endif
>> +
>> +#if defined(CONFIG_USER_ONLY)  && defined(CONFIG_USER_NATIVE_CALL)
>> +
>> +#define NATIVE_FN_W_3W()           \
>> +    target_ulong arg0, arg1, arg2; \
>> +    arg0 = env->regs[0];           \
>> +    arg1 = env->regs[1];           \
>> +    arg2 = env->regs[2];
>> +
>> +void helper_native_memcpy(CPUARMState *env)
>> +{
>> +    CPUState *cs = env_cpu(env);
>> +    NATIVE_FN_W_3W();
>> +    void *ret;
>> +    void *dest = g2h(cs, arg0);
>> +    void *src = g2h(cs, arg1);
>> +    size_t n = (size_t)arg2;
>> +    ret = memcpy(dest, src, n);
>> +    env->regs[0] = (target_ulong)h2g(ret);
>> +}
>> +
>> +void helper_native_memcmp(CPUARMState *env)
>> +{
>> +    CPUState *cs = env_cpu(env);
>> +    NATIVE_FN_W_3W();
>> +    int ret;
>> +    void *s1 = g2h(cs, arg0);
>> +    void *s2 = g2h(cs, arg1);
>> +    size_t n = (size_t)arg2;
>> +    ret = memcmp(s1, s2, n);
>> +    env->regs[0] = ret;
>> +}
>> +
>> +void helper_native_memset(CPUARMState *env)
>> +{
>> +    CPUState *cs = env_cpu(env);
>> +    NATIVE_FN_W_3W();
>> +    void *ret;
>> +    void *s = g2h(cs, arg0);
>> +    int c = (int)arg1;
>> +    size_t n = (size_t)arg2;
>> +    ret = memset(s, c, n);
>> +    env->regs[0] = (target_ulong)h2g(ret);
>> +}
>> +
>> +#endif
>> diff --git a/target/arm/helper.h b/target/arm/helper.h
>> index 3335c2b10b..57144bf6fb 100644
>> --- a/target/arm/helper.h
>> +++ b/target/arm/helper.h
>> @@ -1038,6 +1038,12 @@ DEF_HELPER_FLAGS_5(gvec_uclamp_s, TCG_CALL_NO_RWG,
>>   DEF_HELPER_FLAGS_5(gvec_uclamp_d, TCG_CALL_NO_RWG,
>>                      void, ptr, ptr, ptr, ptr, i32)
>>   +#if defined(CONFIG_USER_ONLY)  &&
>> defined(CONFIG_USER_NATIVE_CALL)
>> +DEF_HELPER_1(native_memcpy, void, env)
>> +DEF_HELPER_1(native_memcmp, void, env)
>> +DEF_HELPER_1(native_memset, void, env)
>> +#endif
>> +
>>   #ifdef TARGET_AARCH64
>>   #include "tcg/helper-a64.h"
>>   #include "tcg/helper-sve.h"
>> diff --git a/target/arm/tcg/translate-a64.c b/target/arm/tcg/translate-a64.c
>> index 741a608739..a0ae96a2fd 100644
>> --- a/target/arm/tcg/translate-a64.c
>> +++ b/target/arm/tcg/translate-a64.c
>> @@ -35,6 +35,7 @@
>>   #include "cpregs.h"
>>   #include "translate-a64.h"
>>   #include "qemu/atomic128.h"
>> +#include "exec/user/native-func.h"
>>     static TCGv_i64 cpu_X[32];
>>   static TCGv_i64 cpu_pc;
>> @@ -42,6 +43,11 @@ static TCGv_i64 cpu_pc;
>>   /* Load/store exclusive handling */
>>   static TCGv_i64 cpu_exclusive_high;
>>   +#if defined(CONFIG_USER_ONLY) && defined(TARGET_AARCH64) && \
>> +    defined(CONFIG_USER_NATIVE_CALL)
>> +bool native_call_status;
>> +#endif
>> +
>>   static const char *regnames[] = {
>>       "x0", "x1", "x2", "x3", "x4", "x5", "x6", "x7",
>>       "x8", "x9", "x10", "x11", "x12", "x13", "x14", "x15",
>> @@ -2292,6 +2298,12 @@ static void disas_exc(DisasContext *s, uint32_t insn)
>>                   gen_exception_insn_el(s, 0, EXCP_UDEF, syndrome, 2);
>>                   break;
>>               }
>> +#if defined(CONFIG_USER_ONLY)  && defined(CONFIG_USER_NATIVE_CALL)
>> +            else if (imm16 == 0xff) {
>> +                native_call_status = true;
>> +                break;
>> +            }
>> +#endif
>>               gen_ss_advance(s);
>>               gen_exception_insn(s, 4, EXCP_SWI, syndrome);
>>               break;
>> @@ -14203,6 +14215,26 @@ static void aarch64_tr_translate_insn(DisasContextBase *dcbase, CPUState *cpu)
>>       s->fp_access_checked = false;
>>       s->sve_access_checked = false;
>>   +#if defined(CONFIG_USER_ONLY)  &&
>> defined(CONFIG_USER_NATIVE_CALL)
>> +    if (native_call_status) {
>> +        switch (insn) {
>> +        case NATIVE_MEMCPY:
>> +            gen_helper_native_memcpy(cpu_env);
>> +            break;
>> +        case NATIVE_MEMCMP:
>> +            gen_helper_native_memcmp(cpu_env);
>> +            break;
>> +        case NATIVE_MEMSET:
>> +            gen_helper_native_memset(cpu_env);
>> +            break;
>> +        default:
>> +            unallocated_encoding(s);
>> +        }
>> +        native_call_status = false;
>> +        return;
>> +    }
>> +#endif
>> +
>>       if (s->pstate_il) {
>>           /*
>>            * Illegal execution state. This has priority over BTI
>> diff --git a/target/arm/tcg/translate.c b/target/arm/tcg/translate.c
>> index 7468476724..9d84cad960 100644
>> --- a/target/arm/tcg/translate.c
>> +++ b/target/arm/tcg/translate.c
>> @@ -34,7 +34,7 @@
>>   #include "exec/helper-gen.h"
>>   #include "exec/log.h"
>>   #include "cpregs.h"
>> -
>> +#include "exec/user/native-func.h"
>>     #define ENABLE_ARCH_4T    arm_dc_feature(s, ARM_FEATURE_V4T)
>>   #define ENABLE_ARCH_5     arm_dc_feature(s, ARM_FEATURE_V5)
>> @@ -58,6 +58,11 @@ TCGv_i32 cpu_CF, cpu_NF, cpu_VF, cpu_ZF;
>>   TCGv_i64 cpu_exclusive_addr;
>>   TCGv_i64 cpu_exclusive_val;
>>   +#if defined(CONFIG_USER_ONLY) && !defined(TARGET_AARCH64)  \
>> +    && defined(CONFIG_USER_NATIVE_CALL)
>> +bool native_call_status;
>> +#endif
>> +
>>   #include "exec/gen-icount.h"
>>     static const char * const regnames[] =
>> @@ -8576,6 +8581,10 @@ static bool trans_SVC(DisasContext *s, arg_SVC *a)
>>           if (s->fgt_svc) {
>>               uint32_t syndrome = syn_aa32_svc(a->imm, s->thumb);
>>               gen_exception_insn_el(s, 0, EXCP_UDEF, syndrome, 2);
>> +#if defined(CONFIG_USER_ONLY)  && defined(CONFIG_USER_NATIVE_CALL)
>> +        } else if (a->imm == 0xff) {
>> +            native_call_status = true;
>> +#endif
>>           } else {
>>               gen_update_pc(s, curr_insn_len(s));
>>               s->svc_imm = a->imm;
>> @@ -9372,6 +9381,25 @@ static void arm_tr_translate_insn(DisasContextBase *dcbase, CPUState *cpu)
>>       insn = arm_ldl_code(env, &dc->base, pc, dc->sctlr_b);
>>       dc->insn = insn;
>>       dc->base.pc_next = pc + 4;
>> +#if defined(CONFIG_USER_ONLY)  && defined(CONFIG_USER_NATIVE_CALL)
>> +    if (native_call_status) {
>> +        switch (insn) {
>> +        case NATIVE_MEMCPY:
>> +            gen_helper_native_memcpy(cpu_env);
>> +            break;
>> +        case NATIVE_MEMCMP:
>> +            gen_helper_native_memcmp(cpu_env);
>> +            break;
>> +        case NATIVE_MEMSET:
>> +            gen_helper_native_memset(cpu_env);
>> +            break;
>> +        default:
>> +            unallocated_encoding(dc);
>> +        }
>> +        native_call_status = false;
>> +        return;
>> +    }
>> +#endif
>>       disas_arm_insn(dc, insn);
>>         arm_post_translate_insn(dc);
>> diff --git a/target/arm/tcg/translate.h b/target/arm/tcg/translate.h
>> index a9d1f4adc2..2291850f3c 100644
>> --- a/target/arm/tcg/translate.h
>> +++ b/target/arm/tcg/translate.h
>> @@ -161,6 +161,14 @@ extern TCGv_i32 cpu_NF, cpu_ZF, cpu_CF, cpu_VF;
>>   extern TCGv_i64 cpu_exclusive_addr;
>>   extern TCGv_i64 cpu_exclusive_val;
>>   +/*
>> + * Indicate whether the next instruction is a native function call (true)
>> + * or not (false).
>> + */
>> +#if defined(CONFIG_USER_ONLY)  && defined(CONFIG_USER_NATIVE_CALL)
>> +extern bool native_call_status;
>> +#endif
>> +
>>   /*
>>    * Constant expanders for the decoders.
>>    */
>> diff --git a/target/i386/helper.h b/target/i386/helper.h
>> index e627a93107..6c91655887 100644
>> --- a/target/i386/helper.h
>> +++ b/target/i386/helper.h
>> @@ -221,3 +221,9 @@ DEF_HELPER_3(rcrq, tl, env, tl, tl)
>>   #endif
>>     DEF_HELPER_1(rdrand, tl, env)
>> +
>> +#if defined(CONFIG_USER_ONLY)  && defined(CONFIG_USER_NATIVE_CALL)
>> +DEF_HELPER_1(native_memcpy, void, env)
>> +DEF_HELPER_1(native_memcmp, void, env)
>> +DEF_HELPER_1(native_memset, void, env)
>> +#endif
>> diff --git a/target/i386/tcg/translate.c b/target/i386/tcg/translate.c
>> index 91c9c0c478..068f594532 100644
>> --- a/target/i386/tcg/translate.c
>> +++ b/target/i386/tcg/translate.c
>> @@ -33,6 +33,7 @@
>>   #include "helper-tcg.h"
>>     #include "exec/log.h"
>> +#include "exec/user/native-func.h"
>>     #define PREFIX_REPZ   0x01
>>   #define PREFIX_REPNZ  0x02
>> @@ -6806,6 +6807,25 @@ static bool disas_insn(DisasContext *s, CPUState *cpu)
>>       case 0x1d0 ... 0x1fe:
>>           disas_insn_new(s, cpu, b);
>>           break;
>> +    /* One unknown opcode for native call */
>> +#if defined(CONFIG_USER_ONLY)  && defined(CONFIG_USER_NATIVE_CALL)
>> +    case 0x1ff:
>> +        uint16_t sig = x86_lduw_code(env, s);
>> +        switch (sig) {
>> +        case NATIVE_MEMCPY:
>> +            gen_helper_native_memcpy(cpu_env);
>> +            break;
>> +        case NATIVE_MEMSET:
>> +            gen_helper_native_memset(cpu_env);
>> +            break;
>> +        case NATIVE_MEMCMP:
>> +            gen_helper_native_memcmp(cpu_env);
>> +            break;
>> +        default:
>> +            goto unknown_op;
>> +        }
>> +        break;
>> +#endif
>>       default:
>>           goto unknown_op;
>>       }
>> diff --git a/target/i386/tcg/user/meson.build b/target/i386/tcg/user/meson.build
>> index 1df6bc4343..490808bd65 100644
>> --- a/target/i386/tcg/user/meson.build
>> +++ b/target/i386/tcg/user/meson.build
>> @@ -1,4 +1,5 @@
>>   i386_user_ss.add(when: ['CONFIG_TCG', 'CONFIG_USER_ONLY'], if_true: files(
>>     'excp_helper.c',
>>     'seg_helper.c',
>> +  'native_helper.c',
>>   ))
>> diff --git a/target/i386/tcg/user/native_helper.c b/target/i386/tcg/user/native_helper.c
>> new file mode 100644
>> index 0000000000..300e14d71a
>> --- /dev/null
>> +++ b/target/i386/tcg/user/native_helper.c
>> @@ -0,0 +1,63 @@
>> +/*
>> + *  native function call helpers
>> + */
>> +
>> +#include "qemu/osdep.h"
>> +#include "cpu.h"
>> +#include "exec/helper-proto.h"
>> +#include "exec/exec-all.h"
>> +#include "exec/cpu_ldst.h"
>> +#include "tcg/helper-tcg.h"
>> +#include "tcg/seg_helper.h"
>> +
>> +#ifdef TARGET_X86_64
>> +#define NATIVE_FN_W_3W()           \
>> +    target_ulong arg0, arg1, arg2; \
>> +    arg0 = env->regs[R_EDI];       \
>> +    arg1 = env->regs[R_ESI];       \
>> +    arg2 = env->regs[R_EDX];
>> +#else
>> +/* linux x86 has several calling conventions. The following implementation
>> +   is for the most commonly used cdecl calling convention. */
>> +#define NATIVE_FN_W_3W()                                   \
>> +    target_ulong arg0, arg1, arg2;                         \
>> +    arg0 = *(target_ulong *)g2h(cs, env->regs[R_ESP] + 4); \
>> +    arg1 = *(target_ulong *)g2h(cs, env->regs[R_ESP] + 8); \
>> +    arg2 = *(target_ulong *)g2h(cs, env->regs[R_ESP] + 12);
>> +#endif
>> +
>> +void helper_native_memcpy(CPUX86State *env)
>> +{
>> +    CPUState *cs = env_cpu(env);
>> +    NATIVE_FN_W_3W();
>> +    void *ret;
>> +    void *dest = g2h(cs, arg0);
>> +    void *src = g2h(cs, arg1);
>> +    size_t n = (size_t)arg2;
>> +    ret = memcpy(dest, src, n);
>> +    env->regs[R_EAX] = (target_ulong)h2g(ret);
>> +}
>> +
>> +void helper_native_memcmp(CPUX86State *env)
>> +{
>> +    CPUState *cs = env_cpu(env);
>> +    NATIVE_FN_W_3W();
>> +    int ret;
>> +    void *s1 = g2h(cs, arg0);
>> +    void *s2 = g2h(cs, arg1);
>> +    size_t n = (size_t)arg2;
>> +    ret = memcmp(s1, s2, n);
>> +    env->regs[R_EAX] = ret;
>> +}
>> +
>> +void helper_native_memset(CPUX86State *env)
>> +{
>> +    CPUState *cs = env_cpu(env);
>> +    NATIVE_FN_W_3W();
>> +    void *ret;
>> +    void *s = g2h(cs, arg0);
>> +    int c = (int)arg1;
>> +    size_t n = (size_t)arg2;
>> +    ret = memset(s, c, n);
>> +    env->regs[R_EAX] = (target_ulong)h2g(ret);
>> +}
>> diff --git a/target/mips/helper.h b/target/mips/helper.h
>> index de32d82e98..9fa949d78c 100644
>> --- a/target/mips/helper.h
>> +++ b/target/mips/helper.h
>> @@ -589,6 +589,12 @@ DEF_HELPER_FLAGS_3(dmthlip, 0, void, tl, tl, env)
>>   DEF_HELPER_FLAGS_3(wrdsp, 0, void, tl, tl, env)
>>   DEF_HELPER_FLAGS_2(rddsp, 0, tl, tl, env)
>>   +#if defined(CONFIG_USER_ONLY)  &&
>> defined(CONFIG_USER_NATIVE_CALL)
>> +DEF_HELPER_1(native_memcpy, void, env)
>> +DEF_HELPER_1(native_memcmp, void, env)
>> +DEF_HELPER_1(native_memset, void, env)
>> +#endif
>> +
>>   #ifndef CONFIG_USER_ONLY
>>   #include "tcg/sysemu_helper.h.inc"
>>   #endif /* !CONFIG_USER_ONLY */
>> diff --git a/target/mips/tcg/meson.build b/target/mips/tcg/meson.build
>> index 7ee969ec8f..fb1ea64047 100644
>> --- a/target/mips/tcg/meson.build
>> +++ b/target/mips/tcg/meson.build
>> @@ -22,6 +22,7 @@ mips_ss.add(files(
>>     'txx9_translate.c',
>>     'vr54xx_helper.c',
>>     'vr54xx_translate.c',
>> +  'native_helper.c',
>>   ))
>>   mips_ss.add(when: 'TARGET_MIPS64', if_true: files(
>>     'tx79_translate.c',
>> diff --git a/target/mips/tcg/native_helper.c b/target/mips/tcg/native_helper.c
>> new file mode 100644
>> index 0000000000..bfd9c92e17
>> --- /dev/null
>> +++ b/target/mips/tcg/native_helper.c
>> @@ -0,0 +1,55 @@
>> +/*
>> + *  native function call helpers
>> + */
>> +
>> +#include "qemu/osdep.h"
>> +#include "cpu.h"
>> +#include "exec/helper-proto.h"
>> +#include "exec/exec-all.h"
>> +#include "exec/cpu_ldst.h"
>> +
>> +#if defined(CONFIG_USER_ONLY)  && defined(CONFIG_USER_NATIVE_CALL)
>> +
>> +#define NATIVE_FN_W_3W()                   \
>> +    target_ulong arg0, arg1, arg2;         \
>> +    arg0 = env->active_tc.gpr[4]; /*"a0"*/ \
>> +    arg1 = env->active_tc.gpr[5]; /*"a1"*/ \
>> +    arg2 = env->active_tc.gpr[6]; /*"a2"*/
>> +
>> +void helper_native_memcpy(CPUMIPSState *env)
>> +{
>> +    CPUState *cs = env_cpu(env);
>> +    NATIVE_FN_W_3W();
>> +    void *ret;
>> +    void *dest = g2h(cs, arg0);
>> +    void *src = g2h(cs, arg1);
>> +    size_t n = (size_t)arg2;
>> +    ret = memcpy(dest, src, n);
>> +    env->active_tc.gpr[2] = (target_ulong)h2g(ret);
>> +}
>> +
>> +void helper_native_memcmp(CPUMIPSState *env)
>> +{
>> +    CPUState *cs = env_cpu(env);
>> +    NATIVE_FN_W_3W();
>> +    int ret;
>> +    void *s1 = g2h(cs, arg0);
>> +    void *s2 = g2h(cs, arg1);
>> +    size_t n = (size_t)arg2;
>> +    ret = memcmp(s1, s2, n);
>> +    env->active_tc.gpr[2] = ret;
>> +}
>> +
>> +void helper_native_memset(CPUMIPSState *env)
>> +{
>> +    CPUState *cs = env_cpu(env);
>> +    NATIVE_FN_W_3W();
>> +    void *ret;
>> +    void *s = g2h(cs, arg0);
>> +    int c = (int)arg1;
>> +    size_t n = (size_t)arg2;
>> +    ret = memset(s, c, n);
>> +    env->active_tc.gpr[2] = (target_ulong)h2g(ret);
>> +}
>> +
>> +#endif
>> diff --git a/target/mips/tcg/translate.c b/target/mips/tcg/translate.c
>> index a6ca2e5a3b..8236dfba21 100644
>> --- a/target/mips/tcg/translate.c
>> +++ b/target/mips/tcg/translate.c
>> @@ -36,6 +36,7 @@
>>   #include "qemu/qemu-print.h"
>>   #include "fpu_helper.h"
>>   #include "translate.h"
>> +#include "exec/user/native-func.h"
>>     /*
>>    * Many sysemu-only helpers are not reachable for user-only.
>> @@ -13591,7 +13592,26 @@ static void decode_opc_special(CPUMIPSState *env, DisasContext *ctx)
>>           gen_helper_pmon(cpu_env, tcg_constant_i32(sa));
>>   #endif
>>           break;
>> -    case OPC_SYSCALL:
>> +    case OPC_SYSCALL:  /* 00 00 00 0C */
>> +#if defined(CONFIG_USER_ONLY)  && defined(CONFIG_USER_NATIVE_CALL)
>> +        if ((((ctx->opcode) >> 24) & 0xff) == 0x1) {
>> +            uint16_t sig =  (ctx->opcode) >> 8 & 0xffff;
>> +            switch (sig) {
>> +            case NATIVE_MEMCPY:
>> +                gen_helper_native_memcpy(cpu_env);
>> +                break;
>> +            case NATIVE_MEMSET:
>> +                gen_helper_native_memset(cpu_env);
>> +                break;
>> +            case NATIVE_MEMCMP:
>> +                gen_helper_native_memcmp(cpu_env);
>> +                break;
>> +            default:
>> +                gen_reserved_instruction(ctx);
>> +            }
>> +            break;
>> +        }
>> +#endif
>>           generate_exception_end(ctx, EXCP_SYSCALL);
>>           break;
>>       case OPC_BREAK:


-- 
Alex Bennée
Virtualisation Tech Lead @ Linaro
Re: [RFC] Native Library Calls
Posted by LIU Zhiwei 11 months, 2 weeks ago
On 2023/5/31 15:59, Alex Bennée wrote:
> LIU Zhiwei <zhiwei_liu@linux.alibaba.com> writes:
>
>> On 2023/5/30 22:24, Yeqi Fu wrote:
>>> This patch introduces a set of feature instructions for native calls
>>> and provides helpers to translate these instructions to corresponding
>>> native functions. A shared library is also implemented, where native
>>> functions are rewritten as feature instructions. At runtime, user
>>> programs load the shared library, and feature instructions are
>>> executed when native functions are called. This patch is applicable
>>> to user programs with architectures x86, x86_64, arm, aarch64, mips,
>>> and mips64. To build, compile libnative.c into a shared library for
>>> the user program's architecture
>> Hi Yeqi,
>>
>> It's an interesting feature.
>>
>> I want to know how does the user program load the native.so? From your
>> commit message, libnative.c will be compiled to the user program's
>> architecture.
>> Should I link my program with native.so at the link stage?
> The idea would be to use the LD_PRELOAD mechanism to get the translated
> linker to bind the new functions before the rest of the libraries are
> mapped. However I think we need to introduce a control flag to
> qemu-$ARCH to enable it, maybe something like:
>
>    qemu-$ARCH --native-bypass ./libnative.so $PROGNAME
>
> and then QEMU itself can setup LD_PRELOAD in the guest environment
> before it runs the program.

Does it mean that we have to replace all functions of the original 
library(libc.so here)? Only replace a few functions may break other 
functions in the same library.

Zhiwei

>
>> Thanks,
>> Zhiwei
>>
>>>    and run the
>>> '../configure --enable-user-native-call && make' command.
>>>
>>> Signed-off-by: Yeqi Fu <fufuyqqqqqq@gmail.com>
>>> ---
>>>    include/exec/user/native-func.h      |  8 +++
>>>    libnative.c                          | 76 ++++++++++++++++++++++++++++
>>>    meson.build                          |  8 +++
>>>    meson_options.txt                    |  2 +
>>>    scripts/make-config-poison.sh        |  4 +-
>>>    scripts/meson-buildoptions.sh        |  4 ++
>>>    target/arm/helper.c                  | 47 +++++++++++++++++
>>>    target/arm/helper.h                  |  6 +++
>>>    target/arm/tcg/translate-a64.c       | 32 ++++++++++++
>>>    target/arm/tcg/translate.c           | 30 ++++++++++-
>>>    target/arm/tcg/translate.h           |  8 +++
>>>    target/i386/helper.h                 |  6 +++
>>>    target/i386/tcg/translate.c          | 20 ++++++++
>>>    target/i386/tcg/user/meson.build     |  1 +
>>>    target/i386/tcg/user/native_helper.c | 63 +++++++++++++++++++++++
>>>    target/mips/helper.h                 |  6 +++
>>>    target/mips/tcg/meson.build          |  1 +
>>>    target/mips/tcg/native_helper.c      | 55 ++++++++++++++++++++
>>>    target/mips/tcg/translate.c          | 22 +++++++-
>>>    19 files changed, 396 insertions(+), 3 deletions(-)
>>>    create mode 100644 include/exec/user/native-func.h
>>>    create mode 100644 libnative.c
>>>    create mode 100644 target/i386/tcg/user/native_helper.c
>>>    create mode 100644 target/mips/tcg/native_helper.c
>>>
>>> diff --git a/include/exec/user/native-func.h b/include/exec/user/native-func.h
>>> new file mode 100644
>>> index 0000000000..8eaac03299
>>> --- /dev/null
>>> +++ b/include/exec/user/native-func.h
>>> @@ -0,0 +1,8 @@
>>> +#if defined(CONFIG_USER_ONLY)  && defined(CONFIG_USER_NATIVE_CALL)
>>> +#define NATIVE_MEMCPY 0x1001
>>> +#define NATIVE_MEMCMP 0x1002
>>> +#define NATIVE_MEMSET 0x1003
>>> +#define NATIVE_STRCPY 0x1004
>>> +#define NATIVE_STRCMP 0x1005
>>> +#define NATIVE_STRCAT 0x1006
>>> +#endif /* CONFIG_USER_NATIVE_CALL */
>>> diff --git a/libnative.c b/libnative.c
>>> new file mode 100644
>>> index 0000000000..cc65c8270c
>>> --- /dev/null
>>> +++ b/libnative.c
>>> @@ -0,0 +1,76 @@
>>> +#include <stdio.h>
>>> +#include <stdlib.h>
>>> +
>>> +#define NATIVE_MEMCPY 0x1001
>>> +#define NATIVE_MEMCMP 0x1002
>>> +#define NATIVE_MEMSET 0x1003
>>> +#define NATIVE_STRCPY 0x1004
>>> +#define NATIVE_STRCMP 0x1005
>>> +#define NATIVE_STRCAT 0x1006
>>> +
>>> +void *memcpy(void *dest, const void *src, size_t n);
>>> +int memcmp(const void *s1, const void *s2, size_t n);
>>> +void *memset(void *s, int c, size_t n);
>>> +char *strcpy(char *dest, const char *src);
>>> +int strcmp(const char *s1, const char *s2);
>>> +char *strcat(char *dest, const char *src);
>>> +
>>> +#define STR_MACRO(str) #str
>>> +#define STR(num) STR_MACRO(num)
>>> +
>>> +#if defined(TARGET_X86_64) || defined(TARGET_I386)
>>> +
>>> +/* unused opcode */
>>> +#define __PREFIX_INSTR \
>>> +    ".byte 0x0f,0xff;"
>>> +
>>> +#define NATIVE_CALL_EXPR(func) \
>>> +    __PREFIX_INSTR             \
>>> +    ".word " STR(func) ";" : : :
>>> +#endif
>>> +
>>> +#if defined(TARGET_ARM) || defined(TARGET_AARCH64)
>>> +
>>> +/* unused syscall number */
>>> +#define __PREFIX_INSTR \
>>> +    "svc 0xff;"
>>> +
>>> +#define NATIVE_CALL_EXPR(func) \
>>> +    __PREFIX_INSTR             \
>>> +    ".word " STR(func) ";" : : :
>>> +
>>> +#endif
>>> +
>>> +#if defined(TARGET_MIPS) || defined(TARGET_MIPS64)
>>> +
>>> +/* unused bytes in syscall instructions */
>>> +#define NATIVE_CALL_EXPR(func) \
>>> +    ".long " STR((0x1 << 24) + (func << 8) + 0xC) ";" : : :
>>> +
>>> +#endif
>>> +
>>> +void *memcpy(void *dest, const void *src, size_t n)
>>> +{
>>> +    __asm__ volatile(NATIVE_CALL_EXPR(NATIVE_MEMCPY));
>>> +}
>>> +
>>> +int memcmp(const void *s1, const void *s2, size_t n)
>>> +{
>>> +    __asm__ volatile(NATIVE_CALL_EXPR(NATIVE_MEMCMP));
>>> +}
>>> +void *memset(void *s, int c, size_t n)
>>> +{
>>> +    __asm__ volatile(NATIVE_CALL_EXPR(NATIVE_MEMSET));
>>> +}
>>> +char *strcpy(char *dest, const char *src)
>>> +{
>>> +    __asm__ volatile(NATIVE_CALL_EXPR(NATIVE_STRCPY));
>>> +}
>>> +int strcmp(const char *s1, const char *s2)
>>> +{
>>> +    __asm__ volatile(NATIVE_CALL_EXPR(NATIVE_STRCMP));
>>> +}
>>> +char *strcat(char *dest, const char *src)
>>> +{
>>> +    __asm__ volatile(NATIVE_CALL_EXPR(NATIVE_STRCAT));
>>> +}
>>> diff --git a/meson.build b/meson.build
>>> index 0a5cdefd4d..04e99a4f25 100644
>>> --- a/meson.build
>>> +++ b/meson.build
>>> @@ -2012,6 +2012,11 @@ have_virtfs_proxy_helper = get_option('virtfs_proxy_helper') \
>>>        .require(libcap_ng.found(), error_message: 'the virtfs proxy helper requires libcap-ng') \
>>>        .allowed()
>>>    +have_user_native_call = get_option('user_native_call') \
>>> +    .require(have_user, error_message: 'user_native_call requires user') \
>>> +    .require(targetos == 'linux', error_message: 'user_native_call requires Linux') \
>>> +    .allowed()
>>> +
>>>    if get_option('block_drv_ro_whitelist') == ''
>>>      config_host_data.set('CONFIG_BDRV_RO_WHITELIST', '')
>>>    else
>>> @@ -2853,6 +2858,9 @@ foreach target : target_dirs
>>>          error('Target @0@ is only available on a Linux host'.format(target))
>>>        endif
>>>        config_target += { 'CONFIG_LINUX_USER': 'y' }
>>> +    if have_user_native_call
>>> +      config_target += { 'CONFIG_USER_NATIVE_CALL': 'y' }
>>> +    endif
>>>      elif target.endswith('bsd-user')
>>>        if 'CONFIG_BSD' not in config_host
>>>          if default_targets
>>> diff --git a/meson_options.txt b/meson_options.txt
>>> index 90237389e2..148dfc99d8 100644
>>> --- a/meson_options.txt
>>> +++ b/meson_options.txt
>>> @@ -352,3 +352,5 @@ option('slirp_smbd', type : 'feature', value : 'auto',
>>>      option('hexagon_idef_parser', type : 'boolean', value : true,
>>>           description: 'use idef-parser to automatically generate TCG code for the Hexagon frontend')
>>> +option('user_native_call', type : 'feature', value : 'auto',
>>> +       description: 'use native code for user mode emulation')
>>> diff --git a/scripts/make-config-poison.sh b/scripts/make-config-poison.sh
>>> index 1892854261..cf2ba69949 100755
>>> --- a/scripts/make-config-poison.sh
>>> +++ b/scripts/make-config-poison.sh
>>> @@ -5,10 +5,12 @@ if test $# = 0; then
>>>    fi
>>>      # Create list of config switches that should be poisoned in
>>> common code...
>>> -# but filter out CONFIG_TCG and CONFIG_USER_ONLY which are special.
>>> +# but filter out CONFIG_TCG, CONFIG_USER_ONLY and CONFIG_USER_NATIVE_CALL
>>> +# which are special.
>>>    exec sed -n \
>>>      -e' /CONFIG_TCG/d' \
>>>      -e '/CONFIG_USER_ONLY/d' \
>>> +  -e '/CONFIG_USER_NATIVE_CALL/d' \
>>>      -e '/^#define / {' \
>>>      -e    's///' \
>>>      -e    's/ .*//' \
>>> diff --git a/scripts/meson-buildoptions.sh b/scripts/meson-buildoptions.sh
>>> index 5714fd93d9..9e98db9eb7 100644
>>> --- a/scripts/meson-buildoptions.sh
>>> +++ b/scripts/meson-buildoptions.sh
>>> @@ -173,6 +173,8 @@ meson_options_help() {
>>>      printf "%s\n" '  tpm             TPM support'
>>>      printf "%s\n" '  u2f             U2F emulation support'
>>>      printf "%s\n" '  usb-redir       libusbredir support'
>>> +  printf "%s\n" '  user-native-call'
>>> +  printf "%s\n" '                  use native code for user mode emulation'
>>>      printf "%s\n" '  vde             vde network backend support'
>>>      printf "%s\n" '  vdi             vdi image format support'
>>>      printf "%s\n" '  vduse-blk-export'
>>> @@ -472,6 +474,8 @@ _meson_option_parse() {
>>>        --disable-u2f) printf "%s" -Du2f=disabled ;;
>>>        --enable-usb-redir) printf "%s" -Dusb_redir=enabled ;;
>>>        --disable-usb-redir) printf "%s" -Dusb_redir=disabled ;;
>>> +    --enable-user-native-call) printf "%s" -Duser_native_call=enabled ;;
>>> +    --disable-user-native-call) printf "%s" -Duser_native_call=disabled ;;
>>>        --enable-vde) printf "%s" -Dvde=enabled ;;
>>>        --disable-vde) printf "%s" -Dvde=disabled ;;
>>>        --enable-vdi) printf "%s" -Dvdi=enabled ;;
>>> diff --git a/target/arm/helper.c b/target/arm/helper.c
>>> index 0b7fd2e7e6..03fbc3724b 100644
>>> --- a/target/arm/helper.c
>>> +++ b/target/arm/helper.c
>>> @@ -25,6 +25,7 @@
>>>    #include "sysemu/tcg.h"
>>>    #include "qapi/error.h"
>>>    #include "qemu/guest-random.h"
>>> +#include "exec/cpu_ldst.h"
>>>    #ifdef CONFIG_TCG
>>>    #include "semihosting/common-semi.h"
>>>    #endif
>>> @@ -12045,3 +12046,49 @@ void aarch64_sve_change_el(CPUARMState *env, int old_el,
>>>        }
>>>    }
>>>    #endif
>>> +
>>> +#if defined(CONFIG_USER_ONLY)  && defined(CONFIG_USER_NATIVE_CALL)
>>> +
>>> +#define NATIVE_FN_W_3W()           \
>>> +    target_ulong arg0, arg1, arg2; \
>>> +    arg0 = env->regs[0];           \
>>> +    arg1 = env->regs[1];           \
>>> +    arg2 = env->regs[2];
>>> +
>>> +void helper_native_memcpy(CPUARMState *env)
>>> +{
>>> +    CPUState *cs = env_cpu(env);
>>> +    NATIVE_FN_W_3W();
>>> +    void *ret;
>>> +    void *dest = g2h(cs, arg0);
>>> +    void *src = g2h(cs, arg1);
>>> +    size_t n = (size_t)arg2;
>>> +    ret = memcpy(dest, src, n);
>>> +    env->regs[0] = (target_ulong)h2g(ret);
>>> +}
>>> +
>>> +void helper_native_memcmp(CPUARMState *env)
>>> +{
>>> +    CPUState *cs = env_cpu(env);
>>> +    NATIVE_FN_W_3W();
>>> +    int ret;
>>> +    void *s1 = g2h(cs, arg0);
>>> +    void *s2 = g2h(cs, arg1);
>>> +    size_t n = (size_t)arg2;
>>> +    ret = memcmp(s1, s2, n);
>>> +    env->regs[0] = ret;
>>> +}
>>> +
>>> +void helper_native_memset(CPUARMState *env)
>>> +{
>>> +    CPUState *cs = env_cpu(env);
>>> +    NATIVE_FN_W_3W();
>>> +    void *ret;
>>> +    void *s = g2h(cs, arg0);
>>> +    int c = (int)arg1;
>>> +    size_t n = (size_t)arg2;
>>> +    ret = memset(s, c, n);
>>> +    env->regs[0] = (target_ulong)h2g(ret);
>>> +}
>>> +
>>> +#endif
>>> diff --git a/target/arm/helper.h b/target/arm/helper.h
>>> index 3335c2b10b..57144bf6fb 100644
>>> --- a/target/arm/helper.h
>>> +++ b/target/arm/helper.h
>>> @@ -1038,6 +1038,12 @@ DEF_HELPER_FLAGS_5(gvec_uclamp_s, TCG_CALL_NO_RWG,
>>>    DEF_HELPER_FLAGS_5(gvec_uclamp_d, TCG_CALL_NO_RWG,
>>>                       void, ptr, ptr, ptr, ptr, i32)
>>>    +#if defined(CONFIG_USER_ONLY)  &&
>>> defined(CONFIG_USER_NATIVE_CALL)
>>> +DEF_HELPER_1(native_memcpy, void, env)
>>> +DEF_HELPER_1(native_memcmp, void, env)
>>> +DEF_HELPER_1(native_memset, void, env)
>>> +#endif
>>> +
>>>    #ifdef TARGET_AARCH64
>>>    #include "tcg/helper-a64.h"
>>>    #include "tcg/helper-sve.h"
>>> diff --git a/target/arm/tcg/translate-a64.c b/target/arm/tcg/translate-a64.c
>>> index 741a608739..a0ae96a2fd 100644
>>> --- a/target/arm/tcg/translate-a64.c
>>> +++ b/target/arm/tcg/translate-a64.c
>>> @@ -35,6 +35,7 @@
>>>    #include "cpregs.h"
>>>    #include "translate-a64.h"
>>>    #include "qemu/atomic128.h"
>>> +#include "exec/user/native-func.h"
>>>      static TCGv_i64 cpu_X[32];
>>>    static TCGv_i64 cpu_pc;
>>> @@ -42,6 +43,11 @@ static TCGv_i64 cpu_pc;
>>>    /* Load/store exclusive handling */
>>>    static TCGv_i64 cpu_exclusive_high;
>>>    +#if defined(CONFIG_USER_ONLY) && defined(TARGET_AARCH64) && \
>>> +    defined(CONFIG_USER_NATIVE_CALL)
>>> +bool native_call_status;
>>> +#endif
>>> +
>>>    static const char *regnames[] = {
>>>        "x0", "x1", "x2", "x3", "x4", "x5", "x6", "x7",
>>>        "x8", "x9", "x10", "x11", "x12", "x13", "x14", "x15",
>>> @@ -2292,6 +2298,12 @@ static void disas_exc(DisasContext *s, uint32_t insn)
>>>                    gen_exception_insn_el(s, 0, EXCP_UDEF, syndrome, 2);
>>>                    break;
>>>                }
>>> +#if defined(CONFIG_USER_ONLY)  && defined(CONFIG_USER_NATIVE_CALL)
>>> +            else if (imm16 == 0xff) {
>>> +                native_call_status = true;
>>> +                break;
>>> +            }
>>> +#endif
>>>                gen_ss_advance(s);
>>>                gen_exception_insn(s, 4, EXCP_SWI, syndrome);
>>>                break;
>>> @@ -14203,6 +14215,26 @@ static void aarch64_tr_translate_insn(DisasContextBase *dcbase, CPUState *cpu)
>>>        s->fp_access_checked = false;
>>>        s->sve_access_checked = false;
>>>    +#if defined(CONFIG_USER_ONLY)  &&
>>> defined(CONFIG_USER_NATIVE_CALL)
>>> +    if (native_call_status) {
>>> +        switch (insn) {
>>> +        case NATIVE_MEMCPY:
>>> +            gen_helper_native_memcpy(cpu_env);
>>> +            break;
>>> +        case NATIVE_MEMCMP:
>>> +            gen_helper_native_memcmp(cpu_env);
>>> +            break;
>>> +        case NATIVE_MEMSET:
>>> +            gen_helper_native_memset(cpu_env);
>>> +            break;
>>> +        default:
>>> +            unallocated_encoding(s);
>>> +        }
>>> +        native_call_status = false;
>>> +        return;
>>> +    }
>>> +#endif
>>> +
>>>        if (s->pstate_il) {
>>>            /*
>>>             * Illegal execution state. This has priority over BTI
>>> diff --git a/target/arm/tcg/translate.c b/target/arm/tcg/translate.c
>>> index 7468476724..9d84cad960 100644
>>> --- a/target/arm/tcg/translate.c
>>> +++ b/target/arm/tcg/translate.c
>>> @@ -34,7 +34,7 @@
>>>    #include "exec/helper-gen.h"
>>>    #include "exec/log.h"
>>>    #include "cpregs.h"
>>> -
>>> +#include "exec/user/native-func.h"
>>>      #define ENABLE_ARCH_4T    arm_dc_feature(s, ARM_FEATURE_V4T)
>>>    #define ENABLE_ARCH_5     arm_dc_feature(s, ARM_FEATURE_V5)
>>> @@ -58,6 +58,11 @@ TCGv_i32 cpu_CF, cpu_NF, cpu_VF, cpu_ZF;
>>>    TCGv_i64 cpu_exclusive_addr;
>>>    TCGv_i64 cpu_exclusive_val;
>>>    +#if defined(CONFIG_USER_ONLY) && !defined(TARGET_AARCH64)  \
>>> +    && defined(CONFIG_USER_NATIVE_CALL)
>>> +bool native_call_status;
>>> +#endif
>>> +
>>>    #include "exec/gen-icount.h"
>>>      static const char * const regnames[] =
>>> @@ -8576,6 +8581,10 @@ static bool trans_SVC(DisasContext *s, arg_SVC *a)
>>>            if (s->fgt_svc) {
>>>                uint32_t syndrome = syn_aa32_svc(a->imm, s->thumb);
>>>                gen_exception_insn_el(s, 0, EXCP_UDEF, syndrome, 2);
>>> +#if defined(CONFIG_USER_ONLY)  && defined(CONFIG_USER_NATIVE_CALL)
>>> +        } else if (a->imm == 0xff) {
>>> +            native_call_status = true;
>>> +#endif
>>>            } else {
>>>                gen_update_pc(s, curr_insn_len(s));
>>>                s->svc_imm = a->imm;
>>> @@ -9372,6 +9381,25 @@ static void arm_tr_translate_insn(DisasContextBase *dcbase, CPUState *cpu)
>>>        insn = arm_ldl_code(env, &dc->base, pc, dc->sctlr_b);
>>>        dc->insn = insn;
>>>        dc->base.pc_next = pc + 4;
>>> +#if defined(CONFIG_USER_ONLY)  && defined(CONFIG_USER_NATIVE_CALL)
>>> +    if (native_call_status) {
>>> +        switch (insn) {
>>> +        case NATIVE_MEMCPY:
>>> +            gen_helper_native_memcpy(cpu_env);
>>> +            break;
>>> +        case NATIVE_MEMCMP:
>>> +            gen_helper_native_memcmp(cpu_env);
>>> +            break;
>>> +        case NATIVE_MEMSET:
>>> +            gen_helper_native_memset(cpu_env);
>>> +            break;
>>> +        default:
>>> +            unallocated_encoding(dc);
>>> +        }
>>> +        native_call_status = false;
>>> +        return;
>>> +    }
>>> +#endif
>>>        disas_arm_insn(dc, insn);
>>>          arm_post_translate_insn(dc);
>>> diff --git a/target/arm/tcg/translate.h b/target/arm/tcg/translate.h
>>> index a9d1f4adc2..2291850f3c 100644
>>> --- a/target/arm/tcg/translate.h
>>> +++ b/target/arm/tcg/translate.h
>>> @@ -161,6 +161,14 @@ extern TCGv_i32 cpu_NF, cpu_ZF, cpu_CF, cpu_VF;
>>>    extern TCGv_i64 cpu_exclusive_addr;
>>>    extern TCGv_i64 cpu_exclusive_val;
>>>    +/*
>>> + * Indicate whether the next instruction is a native function call (true)
>>> + * or not (false).
>>> + */
>>> +#if defined(CONFIG_USER_ONLY)  && defined(CONFIG_USER_NATIVE_CALL)
>>> +extern bool native_call_status;
>>> +#endif
>>> +
>>>    /*
>>>     * Constant expanders for the decoders.
>>>     */
>>> diff --git a/target/i386/helper.h b/target/i386/helper.h
>>> index e627a93107..6c91655887 100644
>>> --- a/target/i386/helper.h
>>> +++ b/target/i386/helper.h
>>> @@ -221,3 +221,9 @@ DEF_HELPER_3(rcrq, tl, env, tl, tl)
>>>    #endif
>>>      DEF_HELPER_1(rdrand, tl, env)
>>> +
>>> +#if defined(CONFIG_USER_ONLY)  && defined(CONFIG_USER_NATIVE_CALL)
>>> +DEF_HELPER_1(native_memcpy, void, env)
>>> +DEF_HELPER_1(native_memcmp, void, env)
>>> +DEF_HELPER_1(native_memset, void, env)
>>> +#endif
>>> diff --git a/target/i386/tcg/translate.c b/target/i386/tcg/translate.c
>>> index 91c9c0c478..068f594532 100644
>>> --- a/target/i386/tcg/translate.c
>>> +++ b/target/i386/tcg/translate.c
>>> @@ -33,6 +33,7 @@
>>>    #include "helper-tcg.h"
>>>      #include "exec/log.h"
>>> +#include "exec/user/native-func.h"
>>>      #define PREFIX_REPZ   0x01
>>>    #define PREFIX_REPNZ  0x02
>>> @@ -6806,6 +6807,25 @@ static bool disas_insn(DisasContext *s, CPUState *cpu)
>>>        case 0x1d0 ... 0x1fe:
>>>            disas_insn_new(s, cpu, b);
>>>            break;
>>> +    /* One unknown opcode for native call */
>>> +#if defined(CONFIG_USER_ONLY)  && defined(CONFIG_USER_NATIVE_CALL)
>>> +    case 0x1ff:
>>> +        uint16_t sig = x86_lduw_code(env, s);
>>> +        switch (sig) {
>>> +        case NATIVE_MEMCPY:
>>> +            gen_helper_native_memcpy(cpu_env);
>>> +            break;
>>> +        case NATIVE_MEMSET:
>>> +            gen_helper_native_memset(cpu_env);
>>> +            break;
>>> +        case NATIVE_MEMCMP:
>>> +            gen_helper_native_memcmp(cpu_env);
>>> +            break;
>>> +        default:
>>> +            goto unknown_op;
>>> +        }
>>> +        break;
>>> +#endif
>>>        default:
>>>            goto unknown_op;
>>>        }
>>> diff --git a/target/i386/tcg/user/meson.build b/target/i386/tcg/user/meson.build
>>> index 1df6bc4343..490808bd65 100644
>>> --- a/target/i386/tcg/user/meson.build
>>> +++ b/target/i386/tcg/user/meson.build
>>> @@ -1,4 +1,5 @@
>>>    i386_user_ss.add(when: ['CONFIG_TCG', 'CONFIG_USER_ONLY'], if_true: files(
>>>      'excp_helper.c',
>>>      'seg_helper.c',
>>> +  'native_helper.c',
>>>    ))
>>> diff --git a/target/i386/tcg/user/native_helper.c b/target/i386/tcg/user/native_helper.c
>>> new file mode 100644
>>> index 0000000000..300e14d71a
>>> --- /dev/null
>>> +++ b/target/i386/tcg/user/native_helper.c
>>> @@ -0,0 +1,63 @@
>>> +/*
>>> + *  native function call helpers
>>> + */
>>> +
>>> +#include "qemu/osdep.h"
>>> +#include "cpu.h"
>>> +#include "exec/helper-proto.h"
>>> +#include "exec/exec-all.h"
>>> +#include "exec/cpu_ldst.h"
>>> +#include "tcg/helper-tcg.h"
>>> +#include "tcg/seg_helper.h"
>>> +
>>> +#ifdef TARGET_X86_64
>>> +#define NATIVE_FN_W_3W()           \
>>> +    target_ulong arg0, arg1, arg2; \
>>> +    arg0 = env->regs[R_EDI];       \
>>> +    arg1 = env->regs[R_ESI];       \
>>> +    arg2 = env->regs[R_EDX];
>>> +#else
>>> +/* linux x86 has several calling conventions. The following implementation
>>> +   is for the most commonly used cdecl calling convention. */
>>> +#define NATIVE_FN_W_3W()                                   \
>>> +    target_ulong arg0, arg1, arg2;                         \
>>> +    arg0 = *(target_ulong *)g2h(cs, env->regs[R_ESP] + 4); \
>>> +    arg1 = *(target_ulong *)g2h(cs, env->regs[R_ESP] + 8); \
>>> +    arg2 = *(target_ulong *)g2h(cs, env->regs[R_ESP] + 12);
>>> +#endif
>>> +
>>> +void helper_native_memcpy(CPUX86State *env)
>>> +{
>>> +    CPUState *cs = env_cpu(env);
>>> +    NATIVE_FN_W_3W();
>>> +    void *ret;
>>> +    void *dest = g2h(cs, arg0);
>>> +    void *src = g2h(cs, arg1);
>>> +    size_t n = (size_t)arg2;
>>> +    ret = memcpy(dest, src, n);
>>> +    env->regs[R_EAX] = (target_ulong)h2g(ret);
>>> +}
>>> +
>>> +void helper_native_memcmp(CPUX86State *env)
>>> +{
>>> +    CPUState *cs = env_cpu(env);
>>> +    NATIVE_FN_W_3W();
>>> +    int ret;
>>> +    void *s1 = g2h(cs, arg0);
>>> +    void *s2 = g2h(cs, arg1);
>>> +    size_t n = (size_t)arg2;
>>> +    ret = memcmp(s1, s2, n);
>>> +    env->regs[R_EAX] = ret;
>>> +}
>>> +
>>> +void helper_native_memset(CPUX86State *env)
>>> +{
>>> +    CPUState *cs = env_cpu(env);
>>> +    NATIVE_FN_W_3W();
>>> +    void *ret;
>>> +    void *s = g2h(cs, arg0);
>>> +    int c = (int)arg1;
>>> +    size_t n = (size_t)arg2;
>>> +    ret = memset(s, c, n);
>>> +    env->regs[R_EAX] = (target_ulong)h2g(ret);
>>> +}
>>> diff --git a/target/mips/helper.h b/target/mips/helper.h
>>> index de32d82e98..9fa949d78c 100644
>>> --- a/target/mips/helper.h
>>> +++ b/target/mips/helper.h
>>> @@ -589,6 +589,12 @@ DEF_HELPER_FLAGS_3(dmthlip, 0, void, tl, tl, env)
>>>    DEF_HELPER_FLAGS_3(wrdsp, 0, void, tl, tl, env)
>>>    DEF_HELPER_FLAGS_2(rddsp, 0, tl, tl, env)
>>>    +#if defined(CONFIG_USER_ONLY)  &&
>>> defined(CONFIG_USER_NATIVE_CALL)
>>> +DEF_HELPER_1(native_memcpy, void, env)
>>> +DEF_HELPER_1(native_memcmp, void, env)
>>> +DEF_HELPER_1(native_memset, void, env)
>>> +#endif
>>> +
>>>    #ifndef CONFIG_USER_ONLY
>>>    #include "tcg/sysemu_helper.h.inc"
>>>    #endif /* !CONFIG_USER_ONLY */
>>> diff --git a/target/mips/tcg/meson.build b/target/mips/tcg/meson.build
>>> index 7ee969ec8f..fb1ea64047 100644
>>> --- a/target/mips/tcg/meson.build
>>> +++ b/target/mips/tcg/meson.build
>>> @@ -22,6 +22,7 @@ mips_ss.add(files(
>>>      'txx9_translate.c',
>>>      'vr54xx_helper.c',
>>>      'vr54xx_translate.c',
>>> +  'native_helper.c',
>>>    ))
>>>    mips_ss.add(when: 'TARGET_MIPS64', if_true: files(
>>>      'tx79_translate.c',
>>> diff --git a/target/mips/tcg/native_helper.c b/target/mips/tcg/native_helper.c
>>> new file mode 100644
>>> index 0000000000..bfd9c92e17
>>> --- /dev/null
>>> +++ b/target/mips/tcg/native_helper.c
>>> @@ -0,0 +1,55 @@
>>> +/*
>>> + *  native function call helpers
>>> + */
>>> +
>>> +#include "qemu/osdep.h"
>>> +#include "cpu.h"
>>> +#include "exec/helper-proto.h"
>>> +#include "exec/exec-all.h"
>>> +#include "exec/cpu_ldst.h"
>>> +
>>> +#if defined(CONFIG_USER_ONLY)  && defined(CONFIG_USER_NATIVE_CALL)
>>> +
>>> +#define NATIVE_FN_W_3W()                   \
>>> +    target_ulong arg0, arg1, arg2;         \
>>> +    arg0 = env->active_tc.gpr[4]; /*"a0"*/ \
>>> +    arg1 = env->active_tc.gpr[5]; /*"a1"*/ \
>>> +    arg2 = env->active_tc.gpr[6]; /*"a2"*/
>>> +
>>> +void helper_native_memcpy(CPUMIPSState *env)
>>> +{
>>> +    CPUState *cs = env_cpu(env);
>>> +    NATIVE_FN_W_3W();
>>> +    void *ret;
>>> +    void *dest = g2h(cs, arg0);
>>> +    void *src = g2h(cs, arg1);
>>> +    size_t n = (size_t)arg2;
>>> +    ret = memcpy(dest, src, n);
>>> +    env->active_tc.gpr[2] = (target_ulong)h2g(ret);
>>> +}
>>> +
>>> +void helper_native_memcmp(CPUMIPSState *env)
>>> +{
>>> +    CPUState *cs = env_cpu(env);
>>> +    NATIVE_FN_W_3W();
>>> +    int ret;
>>> +    void *s1 = g2h(cs, arg0);
>>> +    void *s2 = g2h(cs, arg1);
>>> +    size_t n = (size_t)arg2;
>>> +    ret = memcmp(s1, s2, n);
>>> +    env->active_tc.gpr[2] = ret;
>>> +}
>>> +
>>> +void helper_native_memset(CPUMIPSState *env)
>>> +{
>>> +    CPUState *cs = env_cpu(env);
>>> +    NATIVE_FN_W_3W();
>>> +    void *ret;
>>> +    void *s = g2h(cs, arg0);
>>> +    int c = (int)arg1;
>>> +    size_t n = (size_t)arg2;
>>> +    ret = memset(s, c, n);
>>> +    env->active_tc.gpr[2] = (target_ulong)h2g(ret);
>>> +}
>>> +
>>> +#endif
>>> diff --git a/target/mips/tcg/translate.c b/target/mips/tcg/translate.c
>>> index a6ca2e5a3b..8236dfba21 100644
>>> --- a/target/mips/tcg/translate.c
>>> +++ b/target/mips/tcg/translate.c
>>> @@ -36,6 +36,7 @@
>>>    #include "qemu/qemu-print.h"
>>>    #include "fpu_helper.h"
>>>    #include "translate.h"
>>> +#include "exec/user/native-func.h"
>>>      /*
>>>     * Many sysemu-only helpers are not reachable for user-only.
>>> @@ -13591,7 +13592,26 @@ static void decode_opc_special(CPUMIPSState *env, DisasContext *ctx)
>>>            gen_helper_pmon(cpu_env, tcg_constant_i32(sa));
>>>    #endif
>>>            break;
>>> -    case OPC_SYSCALL:
>>> +    case OPC_SYSCALL:  /* 00 00 00 0C */
>>> +#if defined(CONFIG_USER_ONLY)  && defined(CONFIG_USER_NATIVE_CALL)
>>> +        if ((((ctx->opcode) >> 24) & 0xff) == 0x1) {
>>> +            uint16_t sig =  (ctx->opcode) >> 8 & 0xffff;
>>> +            switch (sig) {
>>> +            case NATIVE_MEMCPY:
>>> +                gen_helper_native_memcpy(cpu_env);
>>> +                break;
>>> +            case NATIVE_MEMSET:
>>> +                gen_helper_native_memset(cpu_env);
>>> +                break;
>>> +            case NATIVE_MEMCMP:
>>> +                gen_helper_native_memcmp(cpu_env);
>>> +                break;
>>> +            default:
>>> +                gen_reserved_instruction(ctx);
>>> +            }
>>> +            break;
>>> +        }
>>> +#endif
>>>            generate_exception_end(ctx, EXCP_SYSCALL);
>>>            break;
>>>        case OPC_BREAK:
>

Re: [RFC] Native Library Calls
Posted by Peter Maydell 11 months, 2 weeks ago
On Tue, 30 May 2023 at 15:26, Yeqi Fu <fufuyqqqqqq@gmail.com> wrote:
>
> This patch introduces a set of feature instructions for native calls
> and provides helpers to translate these instructions to corresponding
> native functions. A shared library is also implemented, where native
> functions are rewritten as feature instructions. At runtime, user
> programs load the shared library, and feature instructions are
> executed when native functions are called. This patch is applicable
> to user programs with architectures x86, x86_64, arm, aarch64, mips,
> and mips64. To build, compile libnative.c into a shared library for
> the user program's architecture and run the
> '../configure --enable-user-native-call && make' command.

So it's essentially providing a QEMU-specific ABI that
(somewhat similarly to semihosting) allows guest programs
to use particular instructions/instruction patterns to
say "do a memcpy/memset/memcmp" ?

Is this an ABI that's implemented by anybody else?

I'm pretty strongly negative about this as an idea -- I don't
think we should be adding QEMU-specific guest-facing ABIs,
especially if the usage of them is likely to be very very low.
At any rate, it needs a lot more justification for why it's
worthwhile than the commit message has provided.

thanks
-- PMM
Re: [RFC] Native Library Calls
Posted by Peter Maydell 11 months, 2 weeks ago
On Tue, 30 May 2023 at 15:46, Peter Maydell <peter.maydell@linaro.org> wrote:
>
> On Tue, 30 May 2023 at 15:26, Yeqi Fu <fufuyqqqqqq@gmail.com> wrote:
> >
> > This patch introduces a set of feature instructions for native calls
> > and provides helpers to translate these instructions to corresponding
> > native functions. A shared library is also implemented, where native
> > functions are rewritten as feature instructions. At runtime, user
> > programs load the shared library, and feature instructions are
> > executed when native functions are called. This patch is applicable
> > to user programs with architectures x86, x86_64, arm, aarch64, mips,
> > and mips64. To build, compile libnative.c into a shared library for
> > the user program's architecture and run the
> > '../configure --enable-user-native-call && make' command.
>
> So it's essentially providing a QEMU-specific ABI that
> (somewhat similarly to semihosting) allows guest programs
> to use particular instructions/instruction patterns to
> say "do a memcpy/memset/memcmp" ?
>
> Is this an ABI that's implemented by anybody else?

Ah, I hadn't realised this was an Outreachy project. I'll
let Alex work with you on iterating on this...

thanks
-- PMM