diff options
author | Andrew G. Morgan <morgan@kernel.org> | 2022-10-23 15:15:30 -0700 |
---|---|---|
committer | Andrew G. Morgan <morgan@kernel.org> | 2022-10-23 15:28:58 -0700 |
commit | 08d48b659aa59d2a5acd9cd13f640f6497718796 (patch) | |
tree | 3f80bf6bf6b929daf7d6aa1752738d4df08d782d | |
parent | 70998415a87587f31063a26a1e52c6f7806b7834 (diff) | |
download | libcap-08d48b659aa59d2a5acd9cd13f640f6497718796.tar.gz |
Add an example of combining Go, C code and "psx" without cgo.
This example was developed while investigating the issues discussed in:
https://bugzilla.kernel.org/show_bug.cgi?id=216610
At this time, it is not possible to build CGO_ENABLED=1 and include
the "psx" package without using its "cgo"-tagged build variant.
This example provides a worked example of doing the opposite: link a
CGO_ENABLED=0 binary with "psx", including some compiled C code.
Signed-off-by: Andrew G. Morgan <morgan@kernel.org>
-rw-r--r-- | contrib/bug216610/Makefile | 23 | ||||
-rw-r--r-- | contrib/bug216610/README.md | 98 | ||||
-rw-r--r-- | contrib/bug216610/c/fib.c | 20 | ||||
-rw-r--r-- | contrib/bug216610/go/.gitignore | 3 | ||||
-rw-r--r-- | contrib/bug216610/go/go.mod | 3 | ||||
-rw-r--r-- | contrib/bug216610/go/main.go | 28 | ||||
-rw-r--r-- | contrib/bug216610/go/vendor/fibber/fib.go | 26 | ||||
-rw-r--r-- | contrib/bug216610/go/vendor/fibber/fibs_linux_amd64.s | 57 |
8 files changed, 258 insertions, 0 deletions
diff --git a/contrib/bug216610/Makefile b/contrib/bug216610/Makefile new file mode 100644 index 0000000..c83284c --- /dev/null +++ b/contrib/bug216610/Makefile @@ -0,0 +1,23 @@ +topdir=$(shell pwd)/../.. +include ../../Make.Rules + +GOTARGET=$(shell eval $$(go env) ; echo $${GOHOSTOS}_$${GOARCH}) + +all: go/fib + +go/fib: go/main.go go/vendor/fibber/fib.syso go/vendor/fibber/fib.go go/vendor/fibber/fibs_linux_amd64.s go/vendor/kernel.org/pub/linux/libs/security/libcap/psx + cd go && CGO_ENABLED=0 go build -o fib main.go + +go/vendor/kernel.org/pub/linux/libs/security/libcap/psx: + mkdir -p go/vendor/kernel.org/pub/linux/libs/security/libcap/ + ln -s $(topdir)/psx $@ + +go/vendor/fibber/fib.syso: c/fib.c + gcc -c -o go/vendor/fibber/fib_$(GOTARGET).syso c/fib.c + +clean: + rm -f *~ + rm -f c/*.o c/*~ + rm -f go/fib go/*~ + rm -f go/vendor/fibber/*.syso go/vendor/fibber/*~ + rm -rf go/vendor/kernel.org diff --git a/contrib/bug216610/README.md b/contrib/bug216610/README.md new file mode 100644 index 0000000..ae59a0d --- /dev/null +++ b/contrib/bug216610/README.md @@ -0,0 +1,98 @@ +# 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 needing the `cgo` variant of `psx` build. However, the go +`"runtime"` package will +[`panic()`](https://cs.opensource.google/go/go/+/refs/tags/go1.19.2:src/runtime/os_linux.go;l=717-720) +if you try this. + +However, in researching that bug, 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_. + +*Caveat Emptor*: this example is potentially very fragile. The Go team +only supports `cgo` linking against C. + +## Content + +In this example we have: +- Some C code, `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/vendor/fibber` that uses this +C compiled compute kernel. +- A top level `Makefile` to build it all. + +This build uses vendored Go packages so I could experiment with +modifications of the `"psx"` package to explore potential changes (of +which there have been none). + +## 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 build a `nocgo` that links +to compute engine style C code. + +## Future thoughts + +At present, this example only works on Linux with `x86_64` (in +go-speak that is `linux_amd64`). This is because I have only provided +some bridging assembly for Go to C calling conventions on that +architecture target (`.../go/vendor/fibber/fibs_linux_amd64.s`). + +Perhaps a later version will have bridging code for all the Go +supported Linux architectures, but it will also have to provide some +mechanism to build the `.../c/fib.c` code to make +`fib_linux_<arch>.syso` files. The [cited +bug](https://bugzilla.kernel.org/show_bug.cgi?id=216610) includes some +pointers for how to use Docker to support this. + +The compilation optimization level for `.../c/fib.c` seems to be +important for this example. Depending on which version of the compiler +is being used, the optimization process can make more or less use of +link-time optimizations, which don't seem to work in this example. For +this reason, we don't include `-O<n>` gcc options when compiling that +C file. + +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/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/go/.gitignore b/contrib/bug216610/go/.gitignore new file mode 100644 index 0000000..68b7ed0 --- /dev/null +++ b/contrib/bug216610/go/.gitignore @@ -0,0 +1,3 @@ +fib +*.syso +vendor/kernel.org diff --git a/contrib/bug216610/go/go.mod b/contrib/bug216610/go/go.mod new file mode 100644 index 0000000..819081e --- /dev/null +++ b/contrib/bug216610/go/go.mod @@ -0,0 +1,3 @@ +module fib + +go 1.18 diff --git a/contrib/bug216610/go/main.go b/contrib/bug216610/go/main.go new file mode 100644 index 0000000..bb5a346 --- /dev/null +++ b/contrib/bug216610/go/main.go @@ -0,0 +1,28 @@ +// Program fib uses the psx package once, and then prints the first +// ten Fibonacci numbers. +package main + +import ( + "fibber" + "fmt" + "log" + "syscall" + + "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/go/vendor/fibber/fib.go b/contrib/bug216610/go/vendor/fibber/fib.go new file mode 100644 index 0000000..e69a309 --- /dev/null +++ b/contrib/bug216610/go/vendor/fibber/fib.go @@ -0,0 +1,26 @@ +package fibber + +import ( + "unsafe" +) + +type State struct { + B, A uint32 +} + +func fibInit(ptr unsafe.Pointer) +func fibNext(ptr unsafe.Pointer) + +// 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{} + fibInit(unsafe.Pointer(&s.B)) + 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() { + fibNext(unsafe.Pointer(&s.B)) +} diff --git a/contrib/bug216610/go/vendor/fibber/fibs_linux_amd64.s b/contrib/bug216610/go/vendor/fibber/fibs_linux_amd64.s new file mode 100644 index 0000000..4e0d800 --- /dev/null +++ b/contrib/bug216610/go/vendor/fibber/fibs_linux_amd64.s @@ -0,0 +1,57 @@ +// 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) + +// Push all of the registers the C callee isn't expected to preserve. +#define PUSHALL() \ + ADJSP $(RINDEX(9)) \ + MOVQ AX, RINDEX(0)(SP) \ + MOVQ CX, RINDEX(1)(SP) \ + MOVQ DX, RINDEX(2)(SP) \ + MOVQ SI, RINDEX(3)(SP) \ + MOVQ DI, RINDEX(4)(SP) \ + MOVQ R8, RINDEX(5)(SP) \ + MOVQ R9, RINDEX(6)(SP) \ + MOVQ R10, RINDEX(7)(SP) \ + MOVQ R11, RINDEX(8)(SP) + +// Pop all of the registers the C callee isn't expected to preserve. +#define POPALL() \ + MOVQ RINDEX(0)(SP), AX \ + MOVQ RINDEX(1)(SP), CX \ + MOVQ RINDEX(2)(SP), DX \ + MOVQ RINDEX(3)(SP), SI \ + MOVQ RINDEX(4)(SP), DI \ + MOVQ RINDEX(5)(SP), R8 \ + MOVQ RINDEX(6)(SP), R9 \ + MOVQ RINDEX(7)(SP), R10 \ + MOVQ RINDEX(8)(SP), R11 \ + ADJSP $-(RINDEX(9)) + +// Header to this function wrapper is the last time we can voluntarily +// yield to some other goroutine. +TEXT ·fibInit(SB),$0-8 + PUSHALL() + MOVQ ptr+RINDEX(0)(FP), DI + CALL fib_init(SB) + POPALL() + RET + +// Header to this function wrapper is the last time we can voluntarily +// yield to some other goroutine. +TEXT ·fibNext(SB),$0-8 + PUSHALL() + MOVQ ptr+RINDEX(0)(FP), DI + CALL fib_next(SB) + POPALL() + RET |