diff mbox series

[RFC,v4,02/11] build: Implement libnative library and the build machinery for libnative

Message ID 20230808141739.3110740-3-fufuyqqqqqq@gmail.com
State New
Headers show
Series Native Library Calls | expand

Commit Message

Yeqi Fu Aug. 8, 2023, 2:17 p.m. UTC
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

Comments

Alex BennΓ©e Aug. 9, 2023, 3:18 p.m. UTC | #1
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);
Richard Henderson Aug. 9, 2023, 4:10 p.m. UTC | #2
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~
diff mbox series

Patch

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);