aboutsummaryrefslogtreecommitdiff
path: root/contrib/bug216610
diff options
context:
space:
mode:
Diffstat (limited to 'contrib/bug216610')
-rw-r--r--contrib/bug216610/.gitignore3
-rw-r--r--contrib/bug216610/Dockerfile13
-rw-r--r--contrib/bug216610/Makefile30
-rw-r--r--contrib/bug216610/README.md139
-rwxr-xr-xcontrib/bug216610/c/build.sh10
-rw-r--r--contrib/bug216610/c/fib.c20
-rwxr-xr-xcontrib/bug216610/c/gcc.sh61
-rw-r--r--contrib/bug216610/go/.gitignore5
-rw-r--r--contrib/bug216610/go/fibber/fib.go32
-rw-r--r--contrib/bug216610/go/fibber/fibs_linux_amd64.s21
-rw-r--r--contrib/bug216610/go/fibber/fibs_linux_arm.s23
-rw-r--r--contrib/bug216610/go/go.mod5
-rw-r--r--contrib/bug216610/go/main.go29
-rwxr-xr-xcontrib/bug216610/mkdocker.sh18
-rwxr-xr-xcontrib/bug216610/package_fns.sh47
15 files changed, 456 insertions, 0 deletions
diff --git a/contrib/bug216610/.gitignore b/contrib/bug216610/.gitignore
new file mode 100644
index 0000000..1478d58
--- /dev/null
+++ b/contrib/bug216610/.gitignore
@@ -0,0 +1,3 @@
+*~
+arms
+Dockerfile
diff --git a/contrib/bug216610/Dockerfile b/contrib/bug216610/Dockerfile
new file mode 100644
index 0000000..5502b71
--- /dev/null
+++ b/contrib/bug216610/Dockerfile
@@ -0,0 +1,13 @@
+FROM debian:latest
+
+# A directory to share files via.
+RUN mkdir /shared
+
+RUN apt-get update
+RUN apt-get install -y gcc-arm-linux-gnueabi binutils-arm-linux-gnueabi
+RUN apt-get install -y gcc-aarch64-linux-gnu binutils-aarch64-linux-gnu
+
+# create a builder user
+RUN echo "builder:x:1000:1000:,,,:/home/builder:/bin/bash" >> /etc/passwd
+RUN echo "builder:*:19289:0:99999:7:::" >> /etc/shadow
+RUN mkdir -p /home/builder && chown builder.bin /home/builder
diff --git a/contrib/bug216610/Makefile b/contrib/bug216610/Makefile
new file mode 100644
index 0000000..ce96fb3
--- /dev/null
+++ b/contrib/bug216610/Makefile
@@ -0,0 +1,30 @@
+topdir=$(shell pwd)/../..
+include ../../Make.Rules
+
+GOTARGET=$(shell eval $$(go env) ; echo $${GOHOSTOS}_$${GOARCH})
+
+all: go/fib
+
+go/fib: go/main.go go/fibber/fib.go go/fibber/linkage.go go/fibber/fibs_$(GOTARGET).s go/fibber/fib_$(GOTARGET).syso
+ cd go && CGO_ENABLED=0 go build
+
+# Build the host native version.
+go/fibber/fib_$(GOTARGET).syso go/fibber/linkage.go: c/fib.c ./c/gcc.sh ./package_fns.sh
+ GCC=gcc ./c/gcc.sh -O3 c/fib.c -c -o go/fibber/fib_$(GOTARGET).syso
+ ./package_fns.sh fibber go/fibber/fib_$(GOTARGET).syso > go/fibber/linkage.go
+
+Dockerfile: Makefile ./mkdocker.sh
+ ./mkdocker.sh > $@
+
+# Use this build target (make arms) to extend support to include arm
+# and arm64 GOARCH values.
+arms: Dockerfile Makefile ./c/gcc.sh ./c/build.sh ./c/fib.c
+ docker run --rm -v $$PWD/c:/shared:z -h debian -u $$(id -u) -it expt shared/build.sh
+ mv c/*.syso go/fibber/
+ touch arms
+
+clean:
+ rm -f *~ arms
+ rm -f c/*.o c/*~
+ rm -f go/fib go/*~
+ rm -f go/fibber/*.syso go/fibber/*~ go/fibber/linkage.go
diff --git a/contrib/bug216610/README.md b/contrib/bug216610/README.md
new file mode 100644
index 0000000..4425715
--- /dev/null
+++ b/contrib/bug216610/README.md
@@ -0,0 +1,139 @@
+# Linking psx and C code without cgo
+
+## Overview
+
+In some embedded situations, there is a desire to compile Go binaries
+to include some C code, but not `libc` etc. For a long time, I had
+assumed this was not possible, since using `cgo` *requires* `libc` and
+`libpthread` linkage.
+
+This _embedded compilation_ need was referenced in a [bug
+filed](https://bugzilla.kernel.org/show_bug.cgi?id=216610) against the
+[`"psx"`](https://pkg.go.dev/kernel.org/pub/linux/libs/security/libcap/psx)
+package. The bug-filer was seeking an alternative to `CGO_ENABLED=1`
+compilation _requiring_ the `cgo` variant of `psx` build. However, the
+go `"runtime"` package will always
+[`panic()`](https://cs.opensource.google/go/go/+/refs/tags/go1.19.2:src/runtime/os_linux.go;l=717-720)
+if you try this because it needs `libpthread` and `[g]libc` to work.
+
+In researching that bug report, however, I have learned there is a
+trick to combining a non-CGO built binary with compiled C code. I
+learned about it from a brief reference in the [Go Programming
+Language
+Wiki](https://zchee.github.io/golang-wiki/GcToolchainTricks/).
+
+This present directory evolved from my attempt to understand and
+hopefully resolve what was going on as reported in that bug into an
+example of this _trick_. I was unable to resolve the problem as
+reported because of the aformentioned `panic()` in the Go
+runtime. However, I was able to demonstrate embedding C code in a Go
+binary _without_ use of cgo. In such a binary, the Go-native version
+of `"psx"` is thus achievable. This is what the example in this
+present directory demonstrates.
+
+*Caveat Emptor*: this example is very fragile. The Go team only
+supports `cgo` linking against C. That being said, I'd certainly like
+to receive bug fixes, etc for this directory if you find you need to
+evolve it to make it work for your use case.
+
+## Content
+
+In this example we have:
+
+- Some C code for the functions `fib_init()` and `fib_next()` that
+combine to implement a _compute engine_ to determine [Fibonacci
+Numbers](https://en.wikipedia.org/wiki/Fibonacci_number). The source
+for this is in the sub directory `c/fib.c`.
+
+- Some Go code, in the directory `go/fibber` that uses this C compiled
+compute kernel.
+
+- `c/gcc.sh` which is a wrapper for `gcc` that adjusts the compilation
+to be digestible by Go's (internal) linker (the one that gets invoked
+when compiling `CGO_ENABLED=0`. Using `gcc` directly instead of this
+wrapper generates an incomplete binary - which miscomputes the
+expected answers. See the discussion below for what seems to be going
+on.
+
+- A top level `Makefile` to build it all.
+
+## Building and running the built binary
+
+Set things up with:
+```
+$ git clone git://git.kernel.org/pub/scm/libs/libcap/libcap.git
+$ cd libcap
+$ make all
+$ cd contrib/bug216610
+$ make clean all
+```
+When you run `./go/fib` it should generate the following output:
+```
+$ ./go/fib
+psx syscall result: PID=<nnnnn>
+fib: 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, ...
+$
+```
+Where `<nnnnn>` is the PID of the program at runtime and will be
+different each time the program is invoked.
+
+## Discussion
+
+The Fibonacci detail of what is going on is mostly uninteresting. The
+reason for developing this example was to explore the build issues in
+the reported [Bug
+216610](https://bugzilla.kernel.org/show_bug.cgi?id=216610). Ultimately,
+this example offers an alternative path to building a `nocgo` program
+that links to compute kernel of C code.
+
+The reason we have added the `c/gcc.sh` wrapper for `gcc` is that
+we've found the Go linker has a hard time digesting the
+cross-sectional `%rip` based data addressing that various optimization
+modes of gcc like to use. Specifically, in the x86_64/amd64
+architecture, if a `R_X86_64_PC32` relocation entry made in a `.text`
+section refers to an `.rodata.cst8` section in a generated `.syso`
+file, the Go linker seems to [replace this reference with a `0` offset
+to
+`(%rip)`](https://github.com/golang/go/issues/24321#issuecomment-1296084103). What
+our wrapper script does is rewrite the generated assembly to store
+these data references to the `.text` section. The Go linker has no
+problem with this _same section_ relative addressing and is able to
+link the resulting objects without problems.
+
+If you want to cross compile, we have support for 32-bit arm
+compilation: what is needed for the Raspberry PI. To get this support,
+try:
+```
+$ make clean all arms
+$ cd go
+$ GOARCH=arm CGO_ENABLED=0 go build
+```
+The generated `fib` binary runs on a 32-bit Raspberry Pi.
+
+## Future thoughts
+
+At present, this example only works on Linux with `x86_64` and `arm`
+build architectures. (In go-speak that is `linux_amd64` and
+`linux_arm`). This is because I have only provided some bridging
+assembly for Go to C calling conventions for those architecture
+targets: `./go/fibber/fibs_linux_amd64.s` and
+`./go/fibber/fibs_linux_arm.s`. The non-native, `make arms`, cross
+compilation requires the `docker` command to be available.
+
+I intend to implement an `arm64` build, when I have a system on which
+to test it.
+
+**Note** The Fedora system on which I've been developing this has some
+ SELINUX impediment to naively using the `docker -v ...` bind mount
+ option. I need the `:z` suffix for bind mounting. I don't know how
+ common an issue this is. On Fedora, building the arm variants of the
+ .syso file can be performed as follows:
+```
+$ docker run --rm -v $PWD/c:/shared:z -h debian -u $(id -u) -it expt shared/build.sh
+```
+
+## Reporting bugs
+
+Please report issues or offer improvements to this example via the
+[Fully Capable `libcap`](https://sites.google.com/site/fullycapable/)
+website.
diff --git a/contrib/bug216610/c/build.sh b/contrib/bug216610/c/build.sh
new file mode 100755
index 0000000..7458fb1
--- /dev/null
+++ b/contrib/bug216610/c/build.sh
@@ -0,0 +1,10 @@
+#!/bin/bash
+#
+# Builds the following .syso files to the directory containing this script:
+#
+# fib_linux_arm.syso
+# fib_linux_arm64.syso
+
+cd ${0%/*}
+GCC=arm-linux-gnueabi-gcc ./gcc.sh -O3 fib.c -c -o fib_linux_arm.syso
+GCC=aarch64-linux-gnu-gcc ./gcc.sh -O3 fib.c -c -o fib_linux_arm64.syso
diff --git a/contrib/bug216610/c/fib.c b/contrib/bug216610/c/fib.c
new file mode 100644
index 0000000..bd665c7
--- /dev/null
+++ b/contrib/bug216610/c/fib.c
@@ -0,0 +1,20 @@
+#include <inttypes.h>
+
+struct state {
+ uint32_t b, a;
+};
+
+void fib_init(struct state *s);
+void fib_init(struct state *s)
+{
+ s->a = 0;
+ s->b = 1;
+}
+
+void fib_next(struct state *s);
+void fib_next(struct state *s)
+{
+ uint32_t next = s->a + s->b;
+ s->a = s->b;
+ s->b = next;
+}
diff --git a/contrib/bug216610/c/gcc.sh b/contrib/bug216610/c/gcc.sh
new file mode 100755
index 0000000..33655d6
--- /dev/null
+++ b/contrib/bug216610/c/gcc.sh
@@ -0,0 +1,61 @@
+#!/bin/bash
+#
+# The Go linker does not seem to know what to do with relative
+# addressing of rodata.* offset from %rip. GCC likes to use this
+# addressing mode on this architecture, so we quickly run into
+# mis-computation when the relative addressing used in a .syso file of
+# symbol located data is resolved to completely the wrong place by the
+# Go (internal) linker.
+#
+# As a workaround for this, we can modify the assembly source code
+# generated by GCC to not point at problematic '.rodata.*' sections,
+# and place this data in the good old '.text' section where Go's
+# linker can make sense of it.
+#
+# This script exists to generate a '.syso' file from some '*.c' files.
+# It works by recognizing the '*.c' command line arguments and
+# converting them into fixed-up '*.s' files. It then performs the
+# compilation for the collection of the '*.s' files. Upon success, it
+# purges the intermediate '*.s' files.
+#
+# The fragile aspect of this present script is which compiler
+# arguments should be used for the compilation from '.c' -> '.s'
+# files. What we do is accumulate arguments until we encounter our
+# first '*.c' file and use those to perform the '.c' -> '.s'
+# compilation. We build up a complete command line for gcc
+# substituting '.s' files for '.c' files in the original command
+# line. Then with the new command line assembled we invoke gcc with
+# those. If that works, we remove all of the intermediate '.s' files.
+
+GCC="${GCC:=gcc}"
+setup=0
+args=()
+final=()
+ses=()
+
+for arg in "$@"; do
+ if [[ "${arg##*.}" = "c" ]]; then
+ setup=1
+ s="${arg%.*}.s"
+ "${GCC}" "${args[@]}" -S -o "${s}" "${arg}"
+ sed -i -e 's/.*\.rodata\..*/\t.text/' "${s}"
+ final+=("${s}")
+ ses+=("${s}")
+ else
+ if [[ $setup -eq 0 ]]; then
+ args+=("${arg}")
+ fi
+ final+=("${arg}")
+ fi
+done
+
+#echo final: "${final[@]}"
+#echo args: "${args[@]}"
+#echo ses: "${ses[@]}"
+
+"${GCC}" "${final[@]}"
+if [[ $? -ne 0 ]]; then
+ echo "failed to compile"
+ exit 1
+fi
+rm -f "${ses[@]}"
diff --git a/contrib/bug216610/go/.gitignore b/contrib/bug216610/go/.gitignore
new file mode 100644
index 0000000..ae14305
--- /dev/null
+++ b/contrib/bug216610/go/.gitignore
@@ -0,0 +1,5 @@
+fib
+*.syso
+main
+go.sum
+linkage.go
diff --git a/contrib/bug216610/go/fibber/fib.go b/contrib/bug216610/go/fibber/fib.go
new file mode 100644
index 0000000..49757cd
--- /dev/null
+++ b/contrib/bug216610/go/fibber/fib.go
@@ -0,0 +1,32 @@
+// Package fibber implements a Fibonacci sequence generator using a C
+// coded compute kernel (a .syso file).
+package fibber
+
+import (
+ "unsafe"
+)
+
+// State is the native Go form of the C.state structure.
+type State struct {
+ B, A uint32
+}
+
+// cPtr converts State into a C pointer suitable as an argument for
+// sysoCaller.
+func (s *State) cPtr() unsafe.Pointer {
+ return unsafe.Pointer(&s.B)
+}
+
+// NewState initializes a Fibonacci Number sequence generator. Upon
+// return s.A=0 and s.B=1 are the first two numbers in the sequence.
+func NewState() *State {
+ s := &State{}
+ syso__fib_init.call(s.cPtr())
+ return s
+}
+
+// Next advances the state to the next number in the sequence. Upon
+// return, s.B is the most recently calculated value.
+func (s *State) Next() {
+ syso__fib_next.call(s.cPtr())
+}
diff --git a/contrib/bug216610/go/fibber/fibs_linux_amd64.s b/contrib/bug216610/go/fibber/fibs_linux_amd64.s
new file mode 100644
index 0000000..5992d09
--- /dev/null
+++ b/contrib/bug216610/go/fibber/fibs_linux_amd64.s
@@ -0,0 +1,21 @@
+// To transition from a Go call to a C function call, we are skating
+// on really thin ice... Ceveat Emptor!
+//
+// Ref:
+// https://gitlab.com/x86-psABIs/x86-64-ABI/-/wikis/home
+//
+// This is not strictly needed, but it makes gdb debugging less
+// confusing because spacer ends up being an alias for the TEXT
+// section start.
+TEXT ·spacer(SB),$0
+ RET
+
+#define RINDEX(n) (8*n)
+
+// Header to this function wrapper is the last time we can voluntarily
+// yield to some other goroutine.
+TEXT ·syso(SB),$0-16
+ MOVQ cFn+RINDEX(0)(FP), SI
+ MOVQ state+RINDEX(1)(FP), DI
+ CALL *SI
+ RET
diff --git a/contrib/bug216610/go/fibber/fibs_linux_arm.s b/contrib/bug216610/go/fibber/fibs_linux_arm.s
new file mode 100644
index 0000000..39640a5
--- /dev/null
+++ b/contrib/bug216610/go/fibber/fibs_linux_arm.s
@@ -0,0 +1,23 @@
+// To transition from a Go call to a C function call, we are skating
+// on really thin ice... Ceveat Emptor!
+//
+// Ref:
+// https://stackoverflow.com/questions/261419/what-registers-to-save-in-the-arm-c-calling-convention
+//
+// This is not strictly needed, but it makes gdb debugging less
+// confusing because spacer ends up being an alias for the TEXT
+// section start.
+TEXT ·spacer(SB),$0
+ RET
+
+#define FINDEX(n) (8*n)
+
+// Header to this function wrapper is the last time we can voluntarily
+// yield to some other goroutine.
+//
+// Conventions: PC == R15, SP == R13, LR == R14, IP (scratch) = R12
+TEXT ·syso(SB),$0-8
+ MOVW cFn+0(FP), R14
+ MOVW state+4(FP), R0
+ BL (R14)
+ RET
diff --git a/contrib/bug216610/go/go.mod b/contrib/bug216610/go/go.mod
new file mode 100644
index 0000000..8531994
--- /dev/null
+++ b/contrib/bug216610/go/go.mod
@@ -0,0 +1,5 @@
+module fib
+
+go 1.18
+
+require kernel.org/pub/linux/libs/security/libcap/psx v1.2.69
diff --git a/contrib/bug216610/go/main.go b/contrib/bug216610/go/main.go
new file mode 100644
index 0000000..65121f6
--- /dev/null
+++ b/contrib/bug216610/go/main.go
@@ -0,0 +1,29 @@
+// Program fib uses the psx package once, and then prints the first
+// ten Fibonacci numbers.
+package main
+
+import (
+ "fmt"
+ "log"
+ "syscall"
+
+ "fib/fibber"
+
+ "kernel.org/pub/linux/libs/security/libcap/psx"
+)
+
+func main() {
+ pid, _, err := psx.Syscall3(syscall.SYS_GETPID, 0, 0, 0)
+ if err != 0 {
+ log.Fatalf("failed to get PID via psx: %v", err)
+ }
+ fmt.Print("psx syscall result: PID=")
+ fmt.Println(pid)
+ s := fibber.NewState()
+ fmt.Print("fib: ", s.A, ", ", s.B)
+ for i := 0; i < 8; i++ {
+ s.Next()
+ fmt.Print(", ", s.B)
+ }
+ fmt.Println(", ...")
+}
diff --git a/contrib/bug216610/mkdocker.sh b/contrib/bug216610/mkdocker.sh
new file mode 100755
index 0000000..860c198
--- /dev/null
+++ b/contrib/bug216610/mkdocker.sh
@@ -0,0 +1,18 @@
+#!/bin/bash
+#
+# This script generates a Dockerfile to be used for cross-compilation
+cat <<EOF
+FROM debian:latest
+
+# A directory to share files via.
+RUN mkdir /shared
+
+RUN apt-get update
+RUN apt-get install -y gcc-arm-linux-gnueabi binutils-arm-linux-gnueabi
+RUN apt-get install -y gcc-aarch64-linux-gnu binutils-aarch64-linux-gnu
+
+# create a builder user
+RUN echo "builder:x:$(id -u):$(id -g):,,,:/home/builder:/bin/bash" >> /etc/passwd
+RUN echo "builder:*:19289:0:99999:7:::" >> /etc/shadow
+RUN mkdir -p /home/builder && chown builder.bin /home/builder
+EOF
diff --git a/contrib/bug216610/package_fns.sh b/contrib/bug216610/package_fns.sh
new file mode 100755
index 0000000..0f4b91c
--- /dev/null
+++ b/contrib/bug216610/package_fns.sh
@@ -0,0 +1,47 @@
+#!/bin/bash
+#
+# Generate some Go code to make calling into the C code of the .syso
+# file easier.
+
+package="${1}"
+syso="${2}"
+
+if [[ -z "${syso}" ]]; then
+ echo "usage: $0 <package> <.....syso>" >&2
+ exit 1
+fi
+
+if [[ "${syso%.syso}" == "${syso}" ]]; then
+ echo "2nd argument should be a .syso file" >&2
+ exit 1
+fi
+
+cat<<EOF
+package ${package}
+
+import (
+ "unsafe"
+)
+
+// syso is how we call, indirectly, into the C-code.
+func syso(cFn, state unsafe.Pointer)
+
+type sysoCaller struct {
+ ptr unsafe.Pointer
+}
+
+// call calls the syso linked C-function, $sym().
+func (s *sysoCaller) call(data unsafe.Pointer) {
+ syso(s.ptr, data)
+}
+EOF
+
+for sym in $(objdump -x "${syso}" | grep -F 'g F' | awk '{print $6}'); do
+ cat<<EOF
+
+//go:linkname _${sym} ${sym}
+var _${sym} byte
+var syso__${sym} = &sysoCaller{ptr: unsafe.Pointer(&_${sym})}
+
+EOF
+done