This commit implements a shared library, where native functions are
rewritten as special instructions. At runtime, user programs load
the shared library, and special instructions are executed when
native functions are called.
Signed-off-by: Yeqi Fu <fufuyqqqqqq@gmail.com>
---
Makefile | 2 +
common-user/native/Makefile.include | 9 ++++
common-user/native/Makefile.target | 22 ++++++++++
common-user/native/libnative.c | 67 +++++++++++++++++++++++++++++
configure | 39 +++++++++++++++++
include/native/libnative.h | 8 ++++
6 files changed, 147 insertions(+)
create mode 100644 common-user/native/Makefile.include
create mode 100644 common-user/native/Makefile.target
create mode 100644 common-user/native/libnative.c
create mode 100644 include/native/libnative.h
diff --git a/Makefile b/Makefile
index 5d48dfac18..6f6147b40f 100644
--- a/Makefile
+++ b/Makefile
@@ -182,6 +182,8 @@ SUBDIR_MAKEFLAGS=$(if $(V),,--no-print-directory --quiet)
include $(SRC_PATH)/tests/Makefile.include
+include $(SRC_PATH)/common-user/native/Makefile.include
+
all: recurse-all
ROMS_RULES=$(foreach t, all clean distclean, $(addsuffix /$(t), $(ROMS)))
diff --git a/common-user/native/Makefile.include b/common-user/native/Makefile.include
new file mode 100644
index 0000000000..40d20bcd4c
--- /dev/null
+++ b/common-user/native/Makefile.include
@@ -0,0 +1,9 @@
+.PHONY: build-native
+build-native: $(NATIVE_TARGETS:%=build-native-library-%)
+$(NATIVE_TARGETS:%=build-native-library-%): build-native-library-%:
+ $(call quiet-command, \
+ $(MAKE) -C common-user/native/$* $(SUBDIR_MAKEFLAGS), \
+ "BUILD","$* native library")
+# endif
+
+all: build-native
diff --git a/common-user/native/Makefile.target b/common-user/native/Makefile.target
new file mode 100644
index 0000000000..0c1241b368
--- /dev/null
+++ b/common-user/native/Makefile.target
@@ -0,0 +1,22 @@
+# -*- Mode: makefile -*-
+#
+# Library for native calls
+#
+
+all:
+-include ../../config-host.mak
+-include config-target.mak
+
+CFLAGS+=-O1 -fPIC -shared -fno-stack-protector -I$(SRC_PATH)/include -D$(TARGET_NAME)
+LDFLAGS+=
+
+SRC = $(SRC_PATH)/common-user/native/libnative.c
+LIBNATIVE = libnative.so
+
+all: $(LIBNATIVE)
+
+$(LIBNATIVE): $(SRC)
+ $(CC) $(CFLAGS) $(EXTRA_CFLAGS) $(EXTRA_NATIVE_CALL_FLAGS) $< -o $@ $(LDFLAGS)
+
+clean:
+ rm -f $(LIBNATIVE)
diff --git a/common-user/native/libnative.c b/common-user/native/libnative.c
new file mode 100644
index 0000000000..662ae6fbfe
--- /dev/null
+++ b/common-user/native/libnative.c
@@ -0,0 +1,67 @@
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "native/libnative.h"
+
+#define WRAP_NATIVE() \
+ do { \
+ __asm__ volatile(__CALL_EXPR : : : "memory"); \
+ } while (0)
+
+#if defined(i386) || defined(x86_64)
+/*
+ * An unused instruction is utilized to mark a native call.
+ */
+#define __CALL_EXPR ".byte 0x0f, 0xff;"
+#endif
+
+#if defined(arm) || defined(aarch64)
+/*
+ * HLT is an invalid instruction for userspace and usefully has 16
+ * bits of spare immeadiate data which we can stuff data in.
+ */
+#define __CALL_EXPR "hlt 0xffff;"
+#endif
+
+#if defined(mips) || defined(mips64)
+/*
+ * The syscall instruction contains 20 unused bits, which are typically
+ * set to 0. These bits can be used to store non-zero data,
+ * distinguishing them from a regular syscall instruction.
+ */
+#define __CALL_EXPR "syscall 0xffff;"
+#endif
+
+void *memcpy(void *dest, const void *src, size_t n)
+{
+ WRAP_NATIVE();
+}
+int memcmp(const void *s1, const void *s2, size_t n)
+{
+ WRAP_NATIVE();
+}
+void *memset(void *s, int c, size_t n)
+{
+ WRAP_NATIVE();
+}
+char *strncpy(char *dest, const char *src, size_t n)
+{
+ WRAP_NATIVE();
+}
+int strncmp(const char *s1, const char *s2, size_t n)
+{
+ WRAP_NATIVE();
+}
+char *strcpy(char *dest, const char *src)
+{
+ WRAP_NATIVE();
+}
+char *strcat(char *dest, const char *src)
+{
+ WRAP_NATIVE();
+}
+int strcmp(const char *s1, const char *s2)
+{
+ WRAP_NATIVE();
+}
diff --git a/configure b/configure
index a076583141..e02fc2c5c0 100755
--- a/configure
+++ b/configure
@@ -1822,6 +1822,45 @@ if test "$tcg" = "enabled"; then
fi
)
+# common-user/native configuration
+(mkdir -p common-user/native
+
+native_targets=
+for target in $target_list; do
+ case $target in
+ *-softmmu)
+ continue
+ ;;
+ esac
+
+ # native call is only supported on these architectures
+ arch=${target%%-*}
+ config_target_mak=common-user/native/$target/config-target.mak
+ case $arch in
+ i386|x86_64|arm|aarch64|mips|mips64)
+ if test -f cross-build/$target/config-target.mak; then
+ mkdir -p "common-user/native/$target"
+ ln -srf cross-build/$target/config-target.mak "$config_target_mak"
+ if test $arch = arm; then
+ echo "EXTRA_NATIVE_CALL_FLAGS=-marm" >> "$config_target_mak"
+ fi
+ if test $arch = $cpu || \
+ { test $arch = i386 && test $cpu = x86_64; } || \
+ { test $arch = arm && test $cpu = aarch64; } || \
+ { test $arch = mips && test $cpu = mips64; }; then
+ echo "LD_PREFIX=/" >> "$config_target_mak"
+ fi
+ echo "LIBNATIVE=$PWD/common-user/native/$target/libnative.so" >> "$config_target_mak"
+ ln -sf $source_path/common-user/native/Makefile.target common-user/native/$target/Makefile
+ native_targets="$native_targets $target"
+ fi
+ ;;
+ esac
+done
+
+echo "NATIVE_TARGETS=$native_targets" >> config-host.mak
+)
+
if test "$skip_meson" = no; then
cross="config-meson.cross.new"
meson_quote() {
diff --git a/include/native/libnative.h b/include/native/libnative.h
new file mode 100644
index 0000000000..ec990d8e5f
--- /dev/null
+++ b/include/native/libnative.h
@@ -0,0 +1,8 @@
+void *memset(void *s, int c, size_t n);
+void *memcpy(void *dest, const void *src, size_t n);
+char *strncpy(char *dest, const char *src, size_t n);
+int memcmp(const void *s1, const void *s2, size_t n);
+int strncmp(const char *s1, const char *s2, size_t n);
+char *strcpy(char *dest, const char *src);
+char *strcat(char *dest, const char *src);
+int strcmp(const char *s1, const char *s2);
--
2.34.1
On 8/8/23 07:17, Yeqi Fu wrote: > +#if defined(i386) || defined(x86_64) > +/* > + * An unused instruction is utilized to mark a native call. > + */ > +#define __CALL_EXPR ".byte 0x0f, 0xff;" > +#endif This is 2 of the 3 (or more) bytes of the UD0 instruction. At minimum you should include a third byte for the modrm. For example, 0F FF C0 ud0 %eax, %eax If you want to encode more data, or simply magic numbers, you can use 0F FF 80 78 56 34 12 ud0 0x12345678(%eax), %eax or with modrm + sib bytes, 0F FF 84 00 78 56 34 12 ud0 0x12345678(%eax, %eax, 0), %eax So you have up to 32 (displacement) + 3 * 3 (registers) + 2 (shift) = 43 bits that you can vary while staying within the encoding of UD0. You can even have the assembler help encode a displacement to associated data: .text 0: ud0 label-0b(%eax), %eax .rodata label: .byte some stuff r~
Yeqi Fu <fufuyqqqqqq@gmail.com> writes:
> This commit implements a shared library, where native functions are
> rewritten as special instructions. At runtime, user programs load
> the shared library, and special instructions are executed when
> native functions are called.
>
> Signed-off-by: Yeqi Fu <fufuyqqqqqq@gmail.com>
> ---
> Makefile | 2 +
> common-user/native/Makefile.include | 9 ++++
> common-user/native/Makefile.target | 22 ++++++++++
> common-user/native/libnative.c | 67 +++++++++++++++++++++++++++++
> configure | 39 +++++++++++++++++
> include/native/libnative.h | 8 ++++
> 6 files changed, 147 insertions(+)
> create mode 100644 common-user/native/Makefile.include
> create mode 100644 common-user/native/Makefile.target
> create mode 100644 common-user/native/libnative.c
> create mode 100644 include/native/libnative.h
>
> diff --git a/Makefile b/Makefile
> index 5d48dfac18..6f6147b40f 100644
> --- a/Makefile
> +++ b/Makefile
> @@ -182,6 +182,8 @@ SUBDIR_MAKEFLAGS=$(if $(V),,--no-print-directory --quiet)
>
> include $(SRC_PATH)/tests/Makefile.include
>
> +include $(SRC_PATH)/common-user/native/Makefile.include
> +
> all: recurse-all
>
> ROMS_RULES=$(foreach t, all clean distclean, $(addsuffix /$(t), $(ROMS)))
> diff --git a/common-user/native/Makefile.include b/common-user/native/Makefile.include
> new file mode 100644
> index 0000000000..40d20bcd4c
> --- /dev/null
> +++ b/common-user/native/Makefile.include
> @@ -0,0 +1,9 @@
> +.PHONY: build-native
> +build-native: $(NATIVE_TARGETS:%=build-native-library-%)
> +$(NATIVE_TARGETS:%=build-native-library-%): build-native-library-%:
> + $(call quiet-command, \
> + $(MAKE) -C common-user/native/$* $(SUBDIR_MAKEFLAGS), \
> + "BUILD","$* native library")
> +# endif
> +
> +all: build-native
> diff --git a/common-user/native/Makefile.target b/common-user/native/Makefile.target
> new file mode 100644
> index 0000000000..0c1241b368
> --- /dev/null
> +++ b/common-user/native/Makefile.target
> @@ -0,0 +1,22 @@
> +# -*- Mode: makefile -*-
> +#
> +# Library for native calls
> +#
> +
> +all:
> +-include ../../config-host.mak
This is sensitive to the out of tree build structure the user chooses. For
example:
➜ pwd
/home/alex/lsrc/qemu.git/builds/user/common-user/native/aarch64-linux-user
🕙16:20:08 alex@zen:common-user/native/aarch64-linux-user on review/native-lib-calls-v4 [$!?]
➜ make libnative.so
make: *** No rule to make target '/common-user/native/libnative.c', needed by 'libnative.so'. Stop.
🕙16:20:13 alex@zen:common-user/native/aarch64-linux-user on review/native-lib-calls-v4 [$!?] [🔴 USAGE]
✗
I think this can be solved the same way as we do for tests/tcg by
symlinking the config-host.mak into place and referring to it directly
or adjusting the include to ../../../config-host.mak because the top of
the build tree has a symlinked copy as well.
> +-include config-target.mak
> +
> +CFLAGS+=-O1 -fPIC -shared -fno-stack-protector -I$(SRC_PATH)/include -D$(TARGET_NAME)
> +LDFLAGS+=
> +
> +SRC = $(SRC_PATH)/common-user/native/libnative.c
> +LIBNATIVE = libnative.so
> +
> +all: $(LIBNATIVE)
> +
> +$(LIBNATIVE): $(SRC)
> + $(CC) $(CFLAGS) $(EXTRA_CFLAGS) $(EXTRA_NATIVE_CALL_FLAGS) $< -o $@ $(LDFLAGS)
> +
> +clean:
> + rm -f $(LIBNATIVE)
> diff --git a/common-user/native/libnative.c b/common-user/native/libnative.c
> new file mode 100644
> index 0000000000..662ae6fbfe
> --- /dev/null
> +++ b/common-user/native/libnative.c
> @@ -0,0 +1,67 @@
> +#include <stdint.h>
> +#include <stdio.h>
> +#include <stdlib.h>
> +
> +#include "native/libnative.h"
> +
> +#define WRAP_NATIVE() \
> + do { \
> + __asm__ volatile(__CALL_EXPR : : : "memory"); \
> + } while (0)
> +
> +#if defined(i386) || defined(x86_64)
> +/*
> + * An unused instruction is utilized to mark a native call.
> + */
> +#define __CALL_EXPR ".byte 0x0f, 0xff;"
> +#endif
> +
> +#if defined(arm) || defined(aarch64)
> +/*
> + * HLT is an invalid instruction for userspace and usefully has 16
> + * bits of spare immeadiate data which we can stuff data in.
> + */
> +#define __CALL_EXPR "hlt 0xffff;"
> +#endif
> +
> +#if defined(mips) || defined(mips64)
> +/*
> + * The syscall instruction contains 20 unused bits, which are typically
> + * set to 0. These bits can be used to store non-zero data,
> + * distinguishing them from a regular syscall instruction.
> + */
> +#define __CALL_EXPR "syscall 0xffff;"
> +#endif
> +
> +void *memcpy(void *dest, const void *src, size_t n)
> +{
> + WRAP_NATIVE();
> +}
> +int memcmp(const void *s1, const void *s2, size_t n)
> +{
> + WRAP_NATIVE();
> +}
> +void *memset(void *s, int c, size_t n)
> +{
> + WRAP_NATIVE();
> +}
> +char *strncpy(char *dest, const char *src, size_t n)
> +{
> + WRAP_NATIVE();
> +}
> +int strncmp(const char *s1, const char *s2, size_t n)
> +{
> + WRAP_NATIVE();
> +}
> +char *strcpy(char *dest, const char *src)
> +{
> + WRAP_NATIVE();
> +}
> +char *strcat(char *dest, const char *src)
> +{
> + WRAP_NATIVE();
> +}
> +int strcmp(const char *s1, const char *s2)
> +{
> + WRAP_NATIVE();
> +}
> diff --git a/configure b/configure
> index a076583141..e02fc2c5c0 100755
> --- a/configure
> +++ b/configure
> @@ -1822,6 +1822,45 @@ if test "$tcg" = "enabled"; then
> fi
> )
>
> +# common-user/native configuration
> +(mkdir -p common-user/native
> +
> +native_targets=
> +for target in $target_list; do
> + case $target in
> + *-softmmu)
> + continue
> + ;;
> + esac
> +
> + # native call is only supported on these architectures
> + arch=${target%%-*}
> + config_target_mak=common-user/native/$target/config-target.mak
> + case $arch in
> + i386|x86_64|arm|aarch64|mips|mips64)
> + if test -f cross-build/$target/config-target.mak; then
> + mkdir -p "common-user/native/$target"
> + ln -srf cross-build/$target/config-target.mak "$config_target_mak"
> + if test $arch = arm; then
> + echo "EXTRA_NATIVE_CALL_FLAGS=-marm" >> "$config_target_mak"
> + fi
> + if test $arch = $cpu || \
> + { test $arch = i386 && test $cpu = x86_64; } || \
> + { test $arch = arm && test $cpu = aarch64; } || \
> + { test $arch = mips && test $cpu = mips64; }; then
> + echo "LD_PREFIX=/" >> "$config_target_mak"
> + fi
> + echo "LIBNATIVE=$PWD/common-user/native/$target/libnative.so" >> "$config_target_mak"
> + ln -sf $source_path/common-user/native/Makefile.target common-user/native/$target/Makefile
> + native_targets="$native_targets $target"
> + fi
> + ;;
> + esac
> +done
> +
> +echo "NATIVE_TARGETS=$native_targets" >> config-host.mak
> +)
> +
> if test "$skip_meson" = no; then
> cross="config-meson.cross.new"
> meson_quote() {
> diff --git a/include/native/libnative.h b/include/native/libnative.h
> new file mode 100644
> index 0000000000..ec990d8e5f
> --- /dev/null
> +++ b/include/native/libnative.h
> @@ -0,0 +1,8 @@
> +void *memset(void *s, int c, size_t n);
> +void *memcpy(void *dest, const void *src, size_t n);
> +char *strncpy(char *dest, const char *src, size_t n);
> +int memcmp(const void *s1, const void *s2, size_t n);
> +int strncmp(const char *s1, const char *s2, size_t n);
> +char *strcpy(char *dest, const char *src);
> +char *strcat(char *dest, const char *src);
> +int strcmp(const char *s1, const char *s2);
--
Alex Bennée
Virtualisation Tech Lead @ Linaro
© 2016 - 2026 Red Hat, Inc.