├── .gitignore ├── test ├── .gitignore ├── nocgo │ ├── .gitignore │ └── Makefile ├── testlib │ ├── .gitignore │ └── Makefile ├── Makefile └── build.go ├── steps ├── 2_go │ ├── test │ │ ├── .gitignore │ │ ├── _obj │ │ │ ├── _cgo_flags │ │ │ ├── _cgo_main.c │ │ │ ├── _cgo_export.c │ │ │ ├── main.cgo1.go │ │ │ ├── _cgo_export.h │ │ │ ├── main.cgo2.c │ │ │ └── _cgo_gotypes.go │ │ └── main.go │ ├── .gitignore │ ├── prog │ │ ├── Makefile │ │ ├── main.s │ │ └── main.go │ ├── main │ ├── Makefile │ ├── README.md │ └── rewrite.go ├── 4_func │ ├── .gitignore │ ├── README.md │ ├── main_amd64.s │ ├── main_386.s │ └── main.go ├── 1_dynamic │ ├── .gitignore │ ├── Makefile │ ├── 42.asm │ ├── README.md │ └── rewrite.go ├── 3_goffi │ ├── .gitignore │ ├── testlib │ │ ├── .gitignore │ │ ├── Makefile │ │ ├── test.go │ │ └── test.c │ ├── prog │ │ ├── Makefile │ │ └── main.go │ ├── README.md │ ├── Makefile │ ├── size │ │ └── pthread.c │ ├── ffi │ │ ├── ffi.h │ │ ├── ffi.go │ │ └── call_linux_amd64.s │ └── rewrite.go ├── 0_static │ ├── simple.c │ ├── .gitignore │ ├── README.md │ ├── 42.asm │ └── Makefile └── README.md ├── examplelibpcap ├── .gitignore ├── Makefile └── main.go ├── example ├── .gitignore ├── testlib │ ├── .gitignore │ ├── test.c │ └── Makefile ├── Makefile └── main.go ├── internal ├── dlopen │ ├── dlopencgo.go │ ├── dlopen_freebsd.go │ ├── dlopen_linux.go │ └── dlopen.go ├── fakecgo │ ├── godefs.go │ ├── libccall.go │ ├── symbols_linux.go │ ├── symbols_freebsd.go │ ├── trampoline_386.s │ ├── trampoline_amd64.s │ ├── libcdefs.go │ ├── symbols.go │ └── cgo.go └── ffi │ ├── call_386.s │ ├── call_386.go │ ├── ffi.go │ ├── call_amd64.go │ └── call_amd64.s ├── util.go ├── doc.go ├── dlopen.go └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode 2 | -------------------------------------------------------------------------------- /test/.gitignore: -------------------------------------------------------------------------------- 1 | *.log 2 | -------------------------------------------------------------------------------- /steps/2_go/test/.gitignore: -------------------------------------------------------------------------------- 1 | test 2 | -------------------------------------------------------------------------------- /steps/4_func/.gitignore: -------------------------------------------------------------------------------- 1 | 4_func 2 | -------------------------------------------------------------------------------- /steps/2_go/.gitignore: -------------------------------------------------------------------------------- 1 | a.out 2 | dyn 3 | *.o 4 | -------------------------------------------------------------------------------- /steps/2_go/test/_obj/_cgo_flags: -------------------------------------------------------------------------------- 1 | _CGO_CFLAGS= 2 | -------------------------------------------------------------------------------- /examplelibpcap/.gitignore: -------------------------------------------------------------------------------- 1 | pcapcgo 2 | pcapnocgo 3 | -------------------------------------------------------------------------------- /steps/1_dynamic/.gitignore: -------------------------------------------------------------------------------- 1 | a.out 2 | dyn 3 | *.o 4 | -------------------------------------------------------------------------------- /steps/3_goffi/.gitignore: -------------------------------------------------------------------------------- 1 | a.out 2 | dyn 3 | main 4 | *.o 5 | -------------------------------------------------------------------------------- /example/.gitignore: -------------------------------------------------------------------------------- 1 | main32 2 | main64 3 | main32C 4 | main64C 5 | -------------------------------------------------------------------------------- /steps/0_static/simple.c: -------------------------------------------------------------------------------- 1 | int _start() { 2 | return 42; 3 | } 4 | -------------------------------------------------------------------------------- /steps/3_goffi/testlib/.gitignore: -------------------------------------------------------------------------------- 1 | testlib 2 | libcalltest.so.1 3 | -------------------------------------------------------------------------------- /steps/0_static/.gitignore: -------------------------------------------------------------------------------- 1 | a.out 2 | simple 3 | simple_debug 4 | *.o 5 | -------------------------------------------------------------------------------- /steps/2_go/prog/Makefile: -------------------------------------------------------------------------------- 1 | prog: main.go main.s 2 | go build -o ../main -------------------------------------------------------------------------------- /steps/3_goffi/prog/Makefile: -------------------------------------------------------------------------------- 1 | prog: main.go 2 | go build -o ../main 3 | -------------------------------------------------------------------------------- /test/nocgo/.gitignore: -------------------------------------------------------------------------------- 1 | nocgo.test32 2 | nocgo.test64 3 | nocgo_test.go 4 | -------------------------------------------------------------------------------- /steps/2_go/main: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/notti/nocgo/HEAD/steps/2_go/main -------------------------------------------------------------------------------- /example/testlib/.gitignore: -------------------------------------------------------------------------------- 1 | testlib 2 | libcalltest32.so.1 3 | libcalltest64.so.1 4 | -------------------------------------------------------------------------------- /steps/2_go/Makefile: -------------------------------------------------------------------------------- 1 | dyn: prog/main.go prog/main.s rewrite.go 2 | $(MAKE) -C prog 3 | go run rewrite.go 4 | -------------------------------------------------------------------------------- /test/testlib/.gitignore: -------------------------------------------------------------------------------- 1 | cgo_bridge.go 2 | cgo_test.go 3 | test.c 4 | libcalltest32.so.1 5 | libcalltest64.so.1 6 | -------------------------------------------------------------------------------- /steps/3_goffi/README.md: -------------------------------------------------------------------------------- 1 | dynamic test a la libffi with go 2 | ============================================================== 3 | 4 | -------------------------------------------------------------------------------- /steps/README.md: -------------------------------------------------------------------------------- 1 | This directories contains all the steps tried one after another to achieve `dlopen` with golang without `cgo` 2 | -------------------------------------------------------------------------------- /steps/1_dynamic/Makefile: -------------------------------------------------------------------------------- 1 | a.out: 42.asm 2 | yasm -f elf -m amd64 -g dwarf2 42.asm 3 | ld 42.o 4 | dyn: a.out rewrite.go 5 | go run rewrite.go 6 | -------------------------------------------------------------------------------- /steps/3_goffi/testlib/Makefile: -------------------------------------------------------------------------------- 1 | libcalltest.so.1: test.c 2 | $(CC) -g -shared -fPIC -Wl,-soname,libcalltest.so.1 -o libcalltest.so.1 test.c -lc 3 | -------------------------------------------------------------------------------- /steps/4_func/README.md: -------------------------------------------------------------------------------- 1 | go function emulation test 2 | ========================== 3 | 4 | Test to see if it would be possible to move from struct to providing actual func -------------------------------------------------------------------------------- /steps/0_static/README.md: -------------------------------------------------------------------------------- 1 | This directory contains a small program that just exits with code 42 in C and assembly. 2 | 3 | This is just for generating base binaries to explore working elf headers - nothing exiting here. -------------------------------------------------------------------------------- /steps/3_goffi/testlib/test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | // int test_call(short a, int b, float c, double d); 4 | import "C" 5 | import "fmt" 6 | 7 | func main() { 8 | fmt.Println(C.test_call(1, 2, 3, 4)) 9 | } 10 | -------------------------------------------------------------------------------- /steps/0_static/42.asm: -------------------------------------------------------------------------------- 1 | ; 42 in 64 bit (afzer tiny.asm) 2 | BITS 64 3 | GLOBAL _start 4 | SECTION .text 5 | _start: 6 | mov rax, 60 7 | mov rdi, 42 8 | syscall 9 | -------------------------------------------------------------------------------- /steps/0_static/Makefile: -------------------------------------------------------------------------------- 1 | a.out: 42.asm 2 | yasm -f elf -m amd64 42.asm 3 | ld -s 42.o 4 | simple_debug: simple.c 5 | gcc -Wall -nostdlib -g -o simple_debug simple.c 6 | simple: simple.c 7 | gcc -Wall -nostdlib -g -o simple simple.c 8 | -------------------------------------------------------------------------------- /test/Makefile: -------------------------------------------------------------------------------- 1 | all: 2 | @go run build.go 3 | @make -C testlib 4 | @make -C nocgo 5 | 6 | verbose: 7 | @make all TEST="-test.v" 8 | 9 | bench: 10 | @make all TEST="-test.bench . -test.benchmem" 11 | 12 | 13 | .PHONY: all 14 | -------------------------------------------------------------------------------- /steps/3_goffi/testlib/test.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | int test_call(unsigned short i1, int i2, float f1, double f2, int i3, int i4, int i5, int i6, int i7, int i8, char i9) { 4 | printf("In C: %d %d %f %f\n", i1, i1, f1, f2); 5 | return i1+i2+f1+f2+i3+i4+i5+i6+i7+i8+i9; 6 | } 7 | -------------------------------------------------------------------------------- /steps/3_goffi/Makefile: -------------------------------------------------------------------------------- 1 | dyn: prog rewrite.go 2 | $(MAKE) -C prog 3 | $(MAKE) -C testlib 4 | go run rewrite.go 5 | LD_LIBRARY_PATH=testlib ./dyn a b c 6 | 7 | libcalltest.so.1: test.c 8 | $(CC) -shared -fPIC -Wl,-soname,libcalltest.so.1 -o libcalltest.so.1 test.c -lc 9 | 10 | .PHONY: dyn 11 | -------------------------------------------------------------------------------- /steps/3_goffi/size/pthread.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | int main(int argc, char *argv[]) { 7 | printf("%d %d %d %d %d\n", sizeof(struct timespec), sizeof(sigset_t), sizeof(pthread_t), sizeof(pthread_attr_t), sizeof(size_t)); 8 | return 0; 9 | } 10 | -------------------------------------------------------------------------------- /example/testlib/test.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | int value; 4 | 5 | int test_call(unsigned short i1, int i2, float f1, double f2, int i3, int i4, int i5, int i6, int i7, int i8, char i9) { 6 | printf("In C: %d %d %f %f\n", i1, i2, f1, f2); 7 | return i1+i2+f1+f2+i3+i4+i5+i6+i7+i8+i9; 8 | } 9 | 10 | void print_value() { 11 | printf("value: %d\n", value); 12 | } 13 | -------------------------------------------------------------------------------- /examplelibpcap/Makefile: -------------------------------------------------------------------------------- 1 | platform_id = $$( uname -s ) 2 | GO_GCFLAGS = $$( \ 3 | case $(platform_id) in \ 4 | (FreeBSD) echo -gcflags=github.com/notti/nocgo/fakecgo=-std ;; \ 5 | esac ) 6 | 7 | all: 8 | @rm -rf pcapnocgo pcapcgo 9 | CGO_ENABLED=0 go build $(GO_GCFLAGS) -o pcapnocgo 10 | CGO_ENABLED=1 go build -o pcapcgo 11 | 12 | clean: 13 | rm -rf pcapnocgo pcapcgo 14 | 15 | .PHONY: clean 16 | -------------------------------------------------------------------------------- /example/testlib/Makefile: -------------------------------------------------------------------------------- 1 | all: libcalltest32.so.1 libcalltest64.so.1 2 | 3 | libcalltest64.so.1: test.c 4 | -$(CC) -g -shared -fPIC -Wl,-soname,libcalltest64.so.1 -o libcalltest64.so.1 test.c -lc 5 | 6 | libcalltest32.so.1: test.c 7 | -$(CC) -g -shared -m32 -fPIC -Wl,-soname,libcalltest32.so.1 -o libcalltest32.so.1 test.c -lc 8 | 9 | clean: 10 | rm -rf libcalltest32.so.1 libcalltest64.so.1 11 | 12 | .PHONY: clean 13 | -------------------------------------------------------------------------------- /test/testlib/Makefile: -------------------------------------------------------------------------------- 1 | all: libcalltest32.so.1 libcalltest64.so.1 test 2 | 3 | libcalltest64.so.1: test.c 4 | -$(CC) -g -shared -fPIC -Wl,-soname,libcalltest64.so.1 -o libcalltest64.so.1 test.c -lc 5 | 6 | libcalltest32.so.1: test.c 7 | -$(CC) -g -shared -m32 -fPIC -Wl,-soname,libcalltest32.so.1 -o libcalltest32.so.1 test.c -lc 8 | 9 | test: 10 | -go test $(TEST) 11 | -CGO_ENABLED=1 GOARCH=386 go test $(TEST) 12 | 13 | .PHONY: test 14 | -------------------------------------------------------------------------------- /steps/2_go/prog/main.s: -------------------------------------------------------------------------------- 1 | #include "textflag.h" 2 | 3 | // func asmcall3() 4 | TEXT ·asmcall3(SB),NOSPLIT,$8 5 | MOVQ DI, 0(SP) // for returning the argument 6 | 7 | MOVQ 0(DI), R8 8 | MOVQ 16(DI), SI 9 | MOVQ 24(DI), DX 10 | MOVQ 8(DI), DI 11 | 12 | XORQ AX, AX // no floating point 13 | 14 | CALL R8 15 | 16 | MOVQ 0(SP), DI 17 | MOVQ AX, 0(DI) // return argument in fn - r1 would get optimized away :() 18 | 19 | RET 20 | -------------------------------------------------------------------------------- /steps/4_func/main_amd64.s: -------------------------------------------------------------------------------- 1 | #include "textflag.h" 2 | #include "funcdata.h" 3 | #include "go_asm.h" 4 | 5 | TEXT ·callWrapper(SB),NOSPLIT|WRAPPER,$24 6 | NO_LOCAL_POINTERS 7 | MOVQ DX, 0(SP) 8 | LEAQ argframe+0(FP), CX 9 | MOVQ CX, 8(SP) 10 | CALL ·fake(SB) 11 | MOVQ SP, AX 12 | ADDQ $24+8+8, AX 13 | MOVQ 0(SP), CX 14 | MOVQ funcStorage_argsize(CX), CX 15 | ADDQ CX, AX 16 | MOVQ 16(SP), BX 17 | MOVQ BX, (AX) 18 | RET 19 | -------------------------------------------------------------------------------- /steps/2_go/test/_obj/_cgo_main.c: -------------------------------------------------------------------------------- 1 | int main() { return 0; } 2 | void crosscall2(void(*fn)(void*, int, __SIZE_TYPE__), void *a, int c, __SIZE_TYPE__ ctxt) { } 3 | __SIZE_TYPE__ _cgo_wait_runtime_init_done() { return 0; } 4 | void _cgo_release_context(__SIZE_TYPE__ ctxt) { } 5 | char* _cgo_topofstack(void) { return (char*)0; } 6 | void _cgo_allocate(void *a, int c) { } 7 | void _cgo_panic(void *a, int c) { } 8 | void _cgo_reginit(void) { } 9 | #line 1 "cgo-generated-wrappers" 10 | -------------------------------------------------------------------------------- /steps/2_go/test/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | // #include 4 | // #include 5 | // #include 6 | import "C" 7 | 8 | import "unsafe" 9 | 10 | func main() { 11 | cs := []byte("Hello\000 world\000") 12 | C.puts((*C.char)(unsafe.Pointer(&cs[0]))) 13 | C.fputs((*C.char)(unsafe.Pointer(&cs[0])), C.stdout) 14 | C.putc('a', C.stdout) 15 | C.strcat((*C.char)(unsafe.Pointer(&cs[0])), (*C.char)(unsafe.Pointer(&cs[0]))) 16 | C.puts((*C.char)(unsafe.Pointer(&cs[0]))) 17 | } 18 | -------------------------------------------------------------------------------- /steps/2_go/test/_obj/_cgo_export.c: -------------------------------------------------------------------------------- 1 | /* Code generated by cmd/cgo; DO NOT EDIT. */ 2 | 3 | #include 4 | #include "_cgo_export.h" 5 | 6 | extern void crosscall2(void (*fn)(void *, int, __SIZE_TYPE__), void *, int, __SIZE_TYPE__); 7 | extern __SIZE_TYPE__ _cgo_wait_runtime_init_done(); 8 | extern void _cgo_release_context(__SIZE_TYPE__); 9 | 10 | extern char* _cgo_topofstack(void); 11 | #define CGO_NO_SANITIZE_THREAD 12 | #define _cgo_tsan_acquire() 13 | #define _cgo_tsan_release() 14 | 15 | 16 | #define _cgo_msan_write(addr, sz) 17 | 18 | -------------------------------------------------------------------------------- /steps/1_dynamic/42.asm: -------------------------------------------------------------------------------- 1 | ; 42 in 64 bit (afzer tiny.asm) 2 | BITS 64 3 | GLOBAL __printf 4 | TYPE __printf object 5 | SECTION .bss 6 | __printf: 7 | DD 0xDEADBEEF 8 | DD 0xDEADBEEF 9 | .end: 10 | size __printf __printf.end-__printf 11 | 12 | SECTION .data 13 | TYPE blah object 14 | GLOBAL blah 15 | blah db 'hello world', 0 16 | .end: 17 | size blah blah.end-blah 18 | 19 | GLOBAL _start 20 | SECTION .text 21 | _start: 22 | mov rdi, blah 23 | call [__printf] 24 | 25 | mov rax, 60 26 | mov rdi, 42 27 | syscall 28 | TYPE _start FUNCTION -------------------------------------------------------------------------------- /internal/dlopen/dlopencgo.go: -------------------------------------------------------------------------------- 1 | // +build cgo 2 | 3 | package dlopen 4 | 5 | // #cgo LDFLAGS: -ldl 6 | // #include 7 | import "C" 8 | import "unsafe" 9 | 10 | func DLOpen(filename []byte, flags int32) uintptr { 11 | return uintptr(C.dlopen((*C.char)(unsafe.Pointer(&filename[0])), C.int(flags))) 12 | } 13 | 14 | func DLClose(handle uintptr) int32 { 15 | return int32(C.dlclose(unsafe.Pointer(handle))) 16 | } 17 | 18 | func DLSym(handle uintptr, symbol []byte) uintptr { 19 | return uintptr(C.dlsym(unsafe.Pointer(handle), (*C.char)(unsafe.Pointer(&symbol[0])))) 20 | } 21 | 22 | func DLError() uintptr { 23 | return uintptr(unsafe.Pointer(C.dlerror())) 24 | } 25 | -------------------------------------------------------------------------------- /test/nocgo/Makefile: -------------------------------------------------------------------------------- 1 | platform_id = $$( uname -s ) 2 | GO_GCFLAGS = $$( \ 3 | case $(platform_id) in \ 4 | (FreeBSD) echo -gcflags=github.com/notti/nocgo/fakecgo=-std ;; \ 5 | esac ) 6 | 7 | all: nocgo.test32 nocgo.test64 8 | 9 | nocgo.test32: 10 | @rm -f nocgo.test32 11 | -@CGO_ENABLED=0 GOARCH=386 go test -c -o nocgo.test32 $(GO_GCFLAGS) && LD_LIBRARY_PATH="../testlib" ./nocgo.test32 $(TEST) 12 | 13 | nocgo.test64: 14 | @rm -f nocgo.test64 15 | -@CGO_ENABLED=0 go test -c -o nocgo.test64 $(GO_GCFLAGS) && LD_LIBRARY_PATH="../testlib" ./nocgo.test64 $(TEST) 16 | 17 | clean: 18 | rm -f nocgo.test32 nocgo.test64 19 | 20 | .PHONY: nocgo.test32 nocgo.test64 clean 21 | -------------------------------------------------------------------------------- /steps/4_func/main_386.s: -------------------------------------------------------------------------------- 1 | #include "textflag.h" 2 | #include "funcdata.h" 3 | #include "go_asm.h" 4 | 5 | TEXT ·callWrapper(SB),NOSPLIT|WRAPPER,$16 6 | NO_LOCAL_POINTERS 7 | MOVL DX, 0(SP) 8 | LEAL argframe+0(FP), CX 9 | MOVL CX, 4(SP) 10 | CALL ·fake(SB) 11 | 12 | MOVL SP, AX 13 | ADDL $16+4, AX 14 | MOVL 0(SP), CX 15 | MOVL funcStorage_argsize(CX), DX 16 | ADDL DX, AX 17 | MOVL funcStorage_retsize(CX), DX 18 | MOVL SP, CX 19 | ADDL $8, CX 20 | 21 | next: 22 | MOVL (CX), BX 23 | MOVL BX, (AX) 24 | 25 | ADDL $4, CX 26 | ADDL $4, AX 27 | SUBL $4, DX 28 | 29 | TESTL DX, DX 30 | JNE next 31 | 32 | RET 33 | -------------------------------------------------------------------------------- /internal/dlopen/dlopen_freebsd.go: -------------------------------------------------------------------------------- 1 | // +build !cgo 2 | 3 | package dlopen 4 | 5 | // we have to use the 3 argument format here :( - 2 argument format is only allowed from inside cgo 6 | 7 | //go:cgo_import_dynamic libc_dlopen_x dlopen "libc.so.7" 8 | //go:cgo_import_dynamic libc_dlclose_x dlclose "libc.so.7" 9 | //go:cgo_import_dynamic libc_dlsym_x dlsym "libc.so.7" 10 | //go:cgo_import_dynamic libc_dlerror_x dlerror "libc.so.7" 11 | 12 | // on amd64 we don't need the following line - on 386 we do... 13 | // anyway - with those lines the output is better (but doesn't matter) - without it on amd64 we get multiple DT_NEEDED with "libc.so.6" etc 14 | 15 | //go:cgo_import_dynamic _ _ "libc.so.7" 16 | -------------------------------------------------------------------------------- /internal/dlopen/dlopen_linux.go: -------------------------------------------------------------------------------- 1 | // +build !cgo 2 | 3 | package dlopen 4 | 5 | // we have to use the 3 argument format here :( - 2 argument format is only allowed from inside cgo 6 | 7 | //go:cgo_import_dynamic libc_dlopen_x dlopen "libdl.so.2" 8 | //go:cgo_import_dynamic libc_dlclose_x dlclose "libdl.so.2" 9 | //go:cgo_import_dynamic libc_dlsym_x dlsym "libdl.so.2" 10 | //go:cgo_import_dynamic libc_dlerror_x dlerror "libdl.so.2" 11 | 12 | // on amd64 we don't need the following line - on 386 we do... 13 | // anyway - with those lines the output is better (but doesn't matter) - without it on amd64 we get multiple DT_NEEDED with "libc.so.6" etc 14 | 15 | //go:cgo_import_dynamic _ _ "libdl.so.2" 16 | -------------------------------------------------------------------------------- /steps/2_go/README.md: -------------------------------------------------------------------------------- 1 | ASM static binary -> dynamic loading without linking with `ld` 2 | ============================================================== 3 | 4 | This directory contains the same 42.asm as 0_static, with an added call to `__printf` and argument "hello world" that will be replaced by `puts` from glibc. 5 | 6 | `rewrite.go` takes `a.out` as input and outputs `dyn` with changed elf headers so that: 7 | 1. `interp` is added so we get loaded dynamically 8 | 2. `libc.so.6` is added as needed library 9 | 3. contents of `__printf` in `.bss` get changed to point to `puts` from `libc.so.6` 10 | 4. profit: `./dyn` outputs `hello world`, is statically compiled (without libc!), but can call into the dynamic symbol `puts` -------------------------------------------------------------------------------- /steps/1_dynamic/README.md: -------------------------------------------------------------------------------- 1 | ASM static binary -> dynamic loading without linking with `ld` 2 | ============================================================== 3 | 4 | This directory contains the same 42.asm as 0_static, with an added call to `__printf` and argument "hello world" that will be replaced by `puts` from glibc. 5 | 6 | `rewrite.go` takes `a.out` as input and outputs `dyn` with changed elf headers so that: 7 | 1. `interp` is added so we get loaded dynamically 8 | 2. `libc.so.6` is added as needed library 9 | 3. contents of `__printf` in `.bss` get changed to point to `puts` from `libc.so.6` 10 | 4. profit: `./dyn` outputs `hello world`, is statically compiled (without libc!), but can call into the dynamic symbol `puts` -------------------------------------------------------------------------------- /steps/3_goffi/ffi/ffi.h: -------------------------------------------------------------------------------- 1 | // generated by compile -asmhdr from package ffi 2 | 3 | #define const_type64 0x0 4 | #define const_typeS32 0x1 5 | #define const_typeU32 0x2 6 | #define const_typeS16 0x3 7 | #define const_typeU16 0x4 8 | #define const_typeS8 0x5 9 | #define const_typeU8 0x6 10 | #define const_typeDouble 0x7 11 | #define const_typeFloat 0x8 12 | #define const_typeUnused 0xffff 13 | #define argument__size 4 14 | #define argument_offset 0 15 | #define argument_t 2 16 | #define Spec__size 120 17 | #define Spec_fn 0 18 | #define Spec_base 8 19 | #define Spec_stack 16 20 | #define Spec_intargs 40 21 | #define Spec_xmmargs 64 22 | #define Spec_ret0 96 23 | #define Spec_ret1 100 24 | #define Spec_xmmret0 104 25 | #define Spec_xmmret1 108 26 | #define Spec_rax 112 27 | #define emptyComplex64__size 8 28 | #define emptyComplex64_a 0 29 | -------------------------------------------------------------------------------- /util.go: -------------------------------------------------------------------------------- 1 | package nocgo 2 | 3 | import "unsafe" 4 | 5 | // MakeCString converts the given string to a null terminated C byte-slice/char array 6 | func MakeCString(s string) []byte { 7 | return append([]byte(s), 0) 8 | } 9 | 10 | // MakeGoStringFromPointer converts the given pointer to a null terminated C string to a go string 11 | func MakeGoStringFromPointer(s uintptr) string { 12 | if s == 0 { 13 | return "" 14 | } 15 | bval := (*[1 << 30]byte)(unsafe.Pointer(s)) 16 | return MakeGoStringFromSlice(bval[:]) 17 | } 18 | 19 | // MakeGoStringFromSlice converts the given byte slice containing a null terminated C string to a go string 20 | func MakeGoStringFromSlice(s []byte) string { 21 | if len(s) == 0 { 22 | return "" 23 | } 24 | for i := range s { 25 | if s[i] == 0 { 26 | return string(s[:i]) 27 | } 28 | } 29 | return string(s[:]) 30 | } 31 | -------------------------------------------------------------------------------- /steps/2_go/test/_obj/main.cgo1.go: -------------------------------------------------------------------------------- 1 | // Code generated by cmd/cgo; DO NOT EDIT. 2 | 3 | //line main.go:1:1 4 | package main 5 | 6 | // #include 7 | // #include 8 | // #include 9 | import _ "unsafe" 10 | 11 | import "unsafe" 12 | 13 | func main() { 14 | cs := []byte("Hello\000 world\000") 15 | (_Cfunc_puts)((*_Ctype_char)(unsafe.Pointer(&cs[0]))) 16 | func(_cgo0 *_Ctype_char, _cgo1 *_Ctype_struct__IO_FILE) _Ctype_int {; _cgoCheckPointer(_cgo1); return (_Cfunc_fputs)(_cgo0, _cgo1);}((*_Ctype_char)(unsafe.Pointer(&cs[0])), (_Cmacro_stdout())) 17 | func(_cgo0 _Ctype_int, _cgo1 *_Ctype_struct__IO_FILE) _Ctype_int {; _cgoCheckPointer(_cgo1); return (_Cfunc_putc)(_cgo0, _cgo1);}('a', (_Cmacro_stdout())) 18 | (_Cfunc_strcat)((*_Ctype_char)(unsafe.Pointer(&cs[0])), (*_Ctype_char)(unsafe.Pointer(&cs[0]))) 19 | (_Cfunc_puts)((*_Ctype_char)(unsafe.Pointer(&cs[0]))) 20 | } 21 | -------------------------------------------------------------------------------- /internal/fakecgo/godefs.go: -------------------------------------------------------------------------------- 1 | package fakecgo 2 | 3 | import "unsafe" 4 | 5 | //go:linkname memmove runtime.memmove 6 | func memmove(to, from unsafe.Pointer, n uintptr) 7 | 8 | //go:linkname funcPC runtime.funcPC 9 | func funcPC(f interface{}) uintptr 10 | 11 | // the following struct is cgothreadstart from runtime 12 | type threadstart struct { 13 | g unsafe.Pointer //should be guintptr 14 | tls *uint64 15 | fn unsafe.Pointer 16 | } 17 | 18 | // just enough from the runtime to manipulate g->stack->lo/hi 19 | type stack struct { 20 | lo uintptr 21 | hi uintptr 22 | } 23 | 24 | type g struct { 25 | stack stack 26 | } 27 | 28 | // We actually don't need the full thing - but this is the same as in runtime and makes possible integration simpler 29 | type libcall struct { 30 | fn uintptr 31 | n uintptr 32 | args uintptr 33 | r1 uintptr 34 | r2 uintptr 35 | err uintptr 36 | } 37 | 38 | type libcFunc uintptr 39 | -------------------------------------------------------------------------------- /example/Makefile: -------------------------------------------------------------------------------- 1 | platform_id = $$( uname -s ) 2 | GO_GCFLAGS = $$( \ 3 | case $(platform_id) in \ 4 | (FreeBSD) echo -gcflags=github.com/notti/nocgo/fakecgo=-std ;; \ 5 | esac ) 6 | 7 | all: main32 main32C main64 main64C testlib 8 | 9 | testlib: 10 | $(MAKE) -C testlib 11 | 12 | main32: main.go testlib 13 | rm -f main32 14 | -CGO_ENABLED=0 GOARCH=386 go build $(GO_GCFLAGS) -o main32 main.go && LD_LIBRARY_PATH=testlib ./main32 a b c 15 | 16 | main32C: main.go testlib 17 | rm -f main32C 18 | -CGO_ENABLED=1 GOARCH=386 go build $(GO_GCFLAGS) -o main32C main.go && LD_LIBRARY_PATH=testlib ./main32C a b c 19 | 20 | main64: main.go testlib 21 | rm -f main64 22 | -CGO_ENABLED=0 go build $(GO_GCFLAGS) -o main64 main.go && LD_LIBRARY_PATH=testlib ./main64 a b c 23 | 24 | main64C: main.go testlib 25 | rm -f main64C 26 | -CGO_ENABLED=0 go build $(GO_GCFLAGS) -o main64C main.go && LD_LIBRARY_PATH=testlib ./main64C a b c 27 | 28 | clean: 29 | rm -rf main32 main64 main32C main64C 30 | $(MAKE) -C testlib clean 31 | 32 | .PHONY: main clean main32 main64 testlib 33 | -------------------------------------------------------------------------------- /steps/3_goffi/prog/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "unsafe" 7 | 8 | "github.com/notti/go-dynamic/steps/3_goffi/ffi" 9 | ) 10 | 11 | var puts__dynload uintptr 12 | var test_call__dynload uintptr 13 | 14 | type putsString struct { 15 | s []byte 16 | num int `ffi:"ret"` 17 | } 18 | 19 | type testCall struct { 20 | i1 uint16 21 | i2 int 22 | f1 float32 23 | f2 float64 24 | i3 int 25 | i4 int 26 | i5 int 27 | i6 int 28 | i7 int 29 | i8 int 30 | i9 int16 31 | ret int `ffi:"ret"` 32 | } 33 | 34 | func main() { 35 | fmt.Println(os.Args) // check if startup works 36 | 37 | str := "hello world" 38 | b := append([]byte(str), 0) 39 | 40 | argsP := &putsString{s: b} 41 | specP := ffi.MakeSpec(puts__dynload, argsP) 42 | 43 | fmt.Println(argsP, specP) 44 | specP.Call(unsafe.Pointer(argsP)) 45 | fmt.Println(argsP) 46 | 47 | argsT := &testCall{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, -11, 12} 48 | specT := ffi.MakeSpec(test_call__dynload, argsT) 49 | 50 | fmt.Println(argsT, specT) 51 | specT.Call(unsafe.Pointer(argsT)) 52 | fmt.Println(argsT) 53 | } 54 | -------------------------------------------------------------------------------- /example/main.go: -------------------------------------------------------------------------------- 1 | // Smoke test/example demonstrating accessing global variables and functions from a self compiled library. 2 | package main 3 | 4 | import ( 5 | "fmt" 6 | "log" 7 | "os" 8 | "runtime" 9 | 10 | "github.com/notti/nocgo" 11 | ) 12 | 13 | func main() { 14 | fmt.Println(os.Args) // check if startup works 15 | 16 | var lib string 17 | switch runtime.GOARCH { 18 | case "386": 19 | lib = "libcalltest32.so.1" 20 | case "amd64": 21 | lib = "libcalltest64.so.1" 22 | default: 23 | log.Fatalln("Unknown arch ", runtime.GOARCH) 24 | } 25 | 26 | l, err := nocgo.Open(lib) 27 | if err != nil { 28 | log.Fatal(err) 29 | } 30 | 31 | var testCall func( 32 | i1 uint16, 33 | i2 int32, 34 | f1 float32, 35 | f2 float64, 36 | i3 int32, 37 | i4 int32, 38 | i5 int32, 39 | i6 int32, 40 | i7 int32, 41 | i8 int32, 42 | i9 int16, 43 | ) int32 44 | 45 | var printCall func() 46 | 47 | var testvalue *int 48 | 49 | if err := l.Func("test_call", &testCall); err != nil { 50 | log.Fatal(err) 51 | } 52 | 53 | if err := l.Func("print_value", &printCall); err != nil { 54 | log.Fatal(err) 55 | } 56 | 57 | fmt.Println(testCall(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, -11)) 58 | 59 | printCall() 60 | err = l.Value("value", &testvalue) 61 | if err != nil { 62 | log.Fatal(err) 63 | } 64 | *testvalue = 100 65 | printCall() 66 | } 67 | -------------------------------------------------------------------------------- /steps/2_go/prog/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | "runtime" 7 | "unsafe" 8 | ) 9 | 10 | var puts__dynload uintptr 11 | var strcat__dynload uintptr 12 | 13 | //go:linkname asmcgocall runtime.asmcgocall 14 | func asmcgocall(unsafe.Pointer, uintptr) int32 15 | 16 | //go:linkname entersyscall runtime.entersyscall 17 | func entersyscall() 18 | 19 | //go:linkname exitsyscall runtime.exitsyscall 20 | func exitsyscall() 21 | 22 | func asmcall3() 23 | 24 | func call3(fn uintptr, arg0 uintptr, arg1 uintptr, arg2 uintptr) uintptr { 25 | p := unsafe.Pointer(reflect.ValueOf(asmcall3).Pointer()) 26 | 27 | entersyscall() 28 | asmcgocall(p, uintptr(unsafe.Pointer(&fn))) 29 | exitsyscall() 30 | 31 | runtime.KeepAlive(p) 32 | runtime.KeepAlive(fn) 33 | return fn 34 | } 35 | 36 | var dings uintptr 37 | 38 | func main() { 39 | str := "hello world" 40 | b := append([]byte(str), 0) 41 | 42 | fmt.Println(call3(puts__dynload, uintptr(unsafe.Pointer(&b[0])), 0, 0)) 43 | 44 | runtime.KeepAlive(b) 45 | 46 | teststr := []byte("hello\000world\000") 47 | teststr2 := []byte("C!\000") 48 | fmt.Println(call3(strcat__dynload, uintptr(unsafe.Pointer(&teststr[0])), uintptr(unsafe.Pointer(&teststr2[0])), 0)) 49 | runtime.KeepAlive(teststr2) 50 | 51 | fmt.Println(call3(puts__dynload, uintptr(unsafe.Pointer(&teststr[0])), 0, 0)) 52 | runtime.KeepAlive(teststr) 53 | 54 | fmt.Println(teststr) 55 | } 56 | -------------------------------------------------------------------------------- /internal/dlopen/dlopen.go: -------------------------------------------------------------------------------- 1 | // +build !cgo 2 | 3 | package dlopen 4 | 5 | import ( 6 | "unsafe" 7 | 8 | _ "github.com/notti/nocgo/internal/fakecgo" // get everything we need to fake cgo behaviour if we're not using cgo 9 | "github.com/notti/nocgo/internal/ffi" 10 | ) 11 | 12 | func mustSpec(fn *byte, fun interface{}) { 13 | err := ffi.MakeSpec(uintptr(unsafe.Pointer(fn)), fun) 14 | if err != nil { 15 | panic(err) 16 | } 17 | } 18 | 19 | // on 386 we need to do the dance of cgo_import_dynamic followed by two linknames, 20 | // definining a variable that gets the dynamic symbol, and then derefercing it. 21 | // Othwerwise we get an unknown relocation type error during linking 22 | 23 | //go:linkname libc_dlopen_x libc_dlopen_x 24 | var libc_dlopen_x byte 25 | var libc_dlopen = &libc_dlopen_x 26 | 27 | //go:linkname libc_dlclose_x libc_dlclose_x 28 | var libc_dlclose_x byte 29 | var libc_dlclose = &libc_dlclose_x 30 | 31 | //go:linkname libc_dlsym_x libc_dlsym_x 32 | var libc_dlsym_x byte 33 | var libc_dlsym = &libc_dlsym_x 34 | 35 | //go:linkname libc_dlerror_x libc_dlerror_x 36 | var libc_dlerror_x byte 37 | var libc_dlerror = &libc_dlerror_x 38 | 39 | var DLOpen func(filename []byte, flags int32) uintptr 40 | var DLClose func(handle uintptr) int32 41 | var DLSym func(handle uintptr, symbol []byte) uintptr 42 | var DLError func() uintptr 43 | 44 | func init() { 45 | mustSpec(libc_dlopen, &DLOpen) 46 | mustSpec(libc_dlclose, &DLClose) 47 | mustSpec(libc_dlsym, &DLSym) 48 | mustSpec(libc_dlerror, &DLError) 49 | } 50 | -------------------------------------------------------------------------------- /internal/fakecgo/libccall.go: -------------------------------------------------------------------------------- 1 | package fakecgo 2 | 3 | import "unsafe" 4 | 5 | // wrapper functions to provide go func definitions with arguments 6 | 7 | func asmlibccall6(fn, n, args uintptr) uintptr 8 | 9 | //go:nosplit 10 | func libcCall0(fn *libcFunc) uintptr { 11 | return asmlibccall6(uintptr(unsafe.Pointer(fn)), 0, 0) 12 | } 13 | 14 | //go:nosplit 15 | //go:noinline 16 | func libcCall1(fn *libcFunc, a1 uintptr) uintptr { 17 | return asmlibccall6(uintptr(unsafe.Pointer(fn)), 1, uintptr(unsafe.Pointer(&a1))) 18 | } 19 | 20 | //go:nosplit 21 | //go:noinline 22 | func libcCall2(fn *libcFunc, a1 uintptr, a2 uintptr) uintptr { 23 | return asmlibccall6(uintptr(unsafe.Pointer(fn)), 2, uintptr(unsafe.Pointer(&a1))) 24 | } 25 | 26 | //go:nosplit 27 | //go:noinline 28 | func libcCall3(fn *libcFunc, a1 uintptr, a2 uintptr, a3 uintptr) uintptr { 29 | return asmlibccall6(uintptr(unsafe.Pointer(fn)), 3, uintptr(unsafe.Pointer(&a1))) 30 | } 31 | 32 | //go:nosplit 33 | //go:noinline 34 | func libcCall4(fn *libcFunc, a1 uintptr, a2 uintptr, a3 uintptr, a4 uintptr) uintptr { 35 | return asmlibccall6(uintptr(unsafe.Pointer(fn)), 4, uintptr(unsafe.Pointer(&a1))) 36 | } 37 | 38 | //go:nosplit 39 | //go:noinline 40 | func libcCall5(fn *libcFunc, a1 uintptr, a2 uintptr, a3 uintptr, a4 uintptr, a5 uintptr) uintptr { 41 | return asmlibccall6(uintptr(unsafe.Pointer(fn)), 5, uintptr(unsafe.Pointer(&a1))) 42 | } 43 | 44 | //go:nosplit 45 | //go:noinline 46 | func libcCall6(fn *libcFunc, a1 uintptr, a2 uintptr, a3 uintptr, a4 uintptr, a5 uintptr, a6 uintptr) uintptr { 47 | return asmlibccall6(uintptr(unsafe.Pointer(fn)), 6, uintptr(unsafe.Pointer(&a1))) 48 | } 49 | -------------------------------------------------------------------------------- /internal/fakecgo/symbols_linux.go: -------------------------------------------------------------------------------- 1 | package fakecgo 2 | 3 | // we have to use the 3 argument format here :( - 2 argument format is only allowed from inside cgo 4 | 5 | // pthread_attr_init will get us the wrong version on glibc - but this doesn't matter, since the memory we provide is zeroed - which will lead the correct result again 6 | 7 | //go:cgo_import_dynamic libc_pthread_attr_init_x pthread_attr_init "libpthread.so.0" 8 | //go:cgo_import_dynamic libc_pthread_attr_getstacksize_x pthread_attr_getstacksize "libpthread.so.0" 9 | //go:cgo_import_dynamic libc_pthread_attr_destroy_x pthread_attr_destroy "libpthread.so.0" 10 | //go:cgo_import_dynamic libc_pthread_sigmask_x pthread_sigmask "libpthread.so.0" 11 | //go:cgo_import_dynamic libc_pthread_create_x pthread_create "libpthread.so.0" 12 | //go:cgo_import_dynamic libc_pthread_detach_x pthread_detach "libpthread.so.0" 13 | //go:cgo_import_dynamic libc_setenv_x setenv "libc.so.6" 14 | //go:cgo_import_dynamic libc_unsetenv_x unsetenv "libc.so.6" 15 | //go:cgo_import_dynamic libc_malloc_x malloc "libc.so.6" 16 | //go:cgo_import_dynamic libc_free_x free "libc.so.6" 17 | //go:cgo_import_dynamic libc_nanosleep_x nanosleep "libc.so.6" 18 | //go:cgo_import_dynamic libc_sigfillset_x sigfillset "libc.so.6" 19 | //go:cgo_import_dynamic libc_abort_x abort "libc.so.6" 20 | //go:cgo_import_dynamic libc_dprintf_x dprintf "libc.so.6" 21 | //go:cgo_import_dynamic libc_strerror_x strerror "libc.so.6" 22 | 23 | // on amd64 we don't need the following lines - on 386 we do... 24 | // anyway - with those lines the output is better (but doesn't matter) - without it on amd64 we get multiple DT_NEEDED with "libc.so.6" etc 25 | 26 | //go:cgo_import_dynamic _ _ "libpthread.so.0" 27 | //go:cgo_import_dynamic _ _ "libc.so.6" 28 | -------------------------------------------------------------------------------- /steps/2_go/test/_obj/_cgo_export.h: -------------------------------------------------------------------------------- 1 | /* Code generated by cmd/cgo; DO NOT EDIT. */ 2 | 3 | /* package main */ 4 | 5 | 6 | #line 1 "cgo-builtin-prolog" 7 | 8 | #include /* for ptrdiff_t below */ 9 | 10 | #ifndef GO_CGO_EXPORT_PROLOGUE_H 11 | #define GO_CGO_EXPORT_PROLOGUE_H 12 | 13 | typedef struct { const char *p; ptrdiff_t n; } _GoString_; 14 | 15 | #endif 16 | 17 | /* Start of preamble from import "C" comments. */ 18 | 19 | 20 | 21 | /* End of preamble from import "C" comments. */ 22 | 23 | 24 | /* Start of boilerplate cgo prologue. */ 25 | #line 1 "cgo-gcc-export-header-prolog" 26 | 27 | #ifndef GO_CGO_PROLOGUE_H 28 | #define GO_CGO_PROLOGUE_H 29 | 30 | typedef signed char GoInt8; 31 | typedef unsigned char GoUint8; 32 | typedef short GoInt16; 33 | typedef unsigned short GoUint16; 34 | typedef int GoInt32; 35 | typedef unsigned int GoUint32; 36 | typedef long long GoInt64; 37 | typedef unsigned long long GoUint64; 38 | typedef GoInt64 GoInt; 39 | typedef GoUint64 GoUint; 40 | typedef __SIZE_TYPE__ GoUintptr; 41 | typedef float GoFloat32; 42 | typedef double GoFloat64; 43 | typedef float _Complex GoComplex64; 44 | typedef double _Complex GoComplex128; 45 | 46 | /* 47 | static assertion to make sure the file is being used on architecture 48 | at least with matching size of GoInt. 49 | */ 50 | typedef char _check_for_64_bit_pointer_matching_GoInt[sizeof(void*)==64/8 ? 1:-1]; 51 | 52 | typedef _GoString_ GoString; 53 | typedef void *GoMap; 54 | typedef void *GoChan; 55 | typedef struct { void *t; void *v; } GoInterface; 56 | typedef struct { void *data; GoInt len; GoInt cap; } GoSlice; 57 | 58 | #endif 59 | 60 | /* End of boilerplate cgo prologue. */ 61 | 62 | #ifdef __cplusplus 63 | extern "C" { 64 | #endif 65 | 66 | 67 | #ifdef __cplusplus 68 | } 69 | #endif 70 | -------------------------------------------------------------------------------- /internal/fakecgo/symbols_freebsd.go: -------------------------------------------------------------------------------- 1 | package fakecgo 2 | 3 | import _ "unsafe" // for go:linkname 4 | 5 | // on BSDs we need the following (this is from runtime/cgo/freebsd.go)... which is not allowed outside cgo/stdlib :( - no way around that 6 | // Right now we can fix that with -gcflags=github.com/notti/nocgo/fakecgo=-std during build - but that doesn't seem to work with go test 7 | 8 | //go:linkname _environ environ 9 | //go:linkname _progname __progname 10 | 11 | //go:cgo_export_dynamic environ 12 | //go:cgo_export_dynamic __progname 13 | 14 | var _environ uintptr 15 | var _progname uintptr 16 | 17 | // we have to use the 3 argument format here :( - 2 argument format is only allowed from inside cgo 18 | 19 | //go:cgo_import_dynamic libc_pthread_attr_init_x pthread_attr_init "libpthread.so" 20 | //go:cgo_import_dynamic libc_pthread_attr_getstacksize_x pthread_attr_getstacksize "libpthread.so" 21 | //go:cgo_import_dynamic libc_pthread_attr_destroy_x pthread_attr_destroy "libpthread.so" 22 | //go:cgo_import_dynamic libc_pthread_sigmask_x pthread_sigmask "libpthread.so" 23 | //go:cgo_import_dynamic libc_pthread_create_x pthread_create "libpthread.so" 24 | //go:cgo_import_dynamic libc_pthread_detach_x pthread_detach "libpthread.so" 25 | //go:cgo_import_dynamic libc_setenv_x setenv "libc.so.7" 26 | //go:cgo_import_dynamic libc_unsetenv_x unsetenv "libc.so.7" 27 | //go:cgo_import_dynamic libc_malloc_x malloc "libc.so.7" 28 | //go:cgo_import_dynamic libc_free_x free "libc.so.7" 29 | //go:cgo_import_dynamic libc_nanosleep_x nanosleep "libc.so.7" 30 | //go:cgo_import_dynamic libc_sigfillset_x sigfillset "libc.so.7" 31 | //go:cgo_import_dynamic libc_abort_x abort "libc.so.7" 32 | //go:cgo_import_dynamic libc_dprintf_x dprintf "libc.so.7" 33 | //go:cgo_import_dynamic libc_strerror_x strerror "libc.so.7" 34 | //go:cgo_import_dynamic _ _ "libpthread.so" 35 | //go:cgo_import_dynamic _ _ "libc.so.7" 36 | -------------------------------------------------------------------------------- /steps/4_func/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | "unsafe" 7 | ) 8 | 9 | //go:linkname funcPC runtime.funcPC 10 | func funcPC(f interface{}) uintptr 11 | 12 | func callWrapper() 13 | 14 | type funcStorage struct { 15 | code uintptr 16 | argsize uintptr 17 | retsize uintptr 18 | value int 19 | } 20 | 21 | func fake(s *funcStorage, i *[2]int) int64 { 22 | fmt.Println("in fake: ", s.value, i[0], i[1]) 23 | return int64(i[0] + 100 + s.value) 24 | } 25 | 26 | var alignSize = unsafe.Sizeof(uintptr(0)) 27 | 28 | func emulate(x reflect.Value) { 29 | ftype := x.Elem().Type() 30 | fptr := x.Pointer() 31 | fmt.Println(fptr, ftype) 32 | toassign := new(funcStorage) 33 | toassign.code = funcPC(callWrapper) 34 | for i := 0; i < ftype.NumIn(); i++ { 35 | size := ftype.In(i).Size() 36 | size = (size + alignSize - 1) &^ (alignSize - 1) 37 | toassign.argsize += size 38 | } 39 | for i := 0; i < ftype.NumOut(); i++ { 40 | size := ftype.Out(i).Size() 41 | size = (size + alignSize - 1) &^ (alignSize - 1) 42 | toassign.retsize += size 43 | } 44 | toassign.value = -5 45 | *(*unsafe.Pointer)(unsafe.Pointer(fptr)) = unsafe.Pointer(toassign) 46 | fmt.Println(toassign) 47 | } 48 | 49 | func main() { 50 | var test func(int, byte, int) int64 51 | var test1 func(int, int) int64 52 | var test2 func(int, string) int64 53 | 54 | emulate(reflect.ValueOf(&test)) 55 | emulate(reflect.ValueOf(&test1)) 56 | emulate(reflect.ValueOf(&test2)) 57 | 58 | fmt.Println("test 1: ", test(10, 1, 2)) 59 | fmt.Println("test 2: ", test(11, 2, 3)) 60 | fmt.Println("test 3: ", test(12, 3, 4)) 61 | 62 | fmt.Println("test 1: ", test1(10, 1)) 63 | fmt.Println("test 2: ", test1(11, 2)) 64 | fmt.Println("test 3: ", test1(12, 3)) 65 | 66 | fmt.Println("test2 1: ", test2(10, "1, 2")) 67 | fmt.Println("test2 2: ", test2(11, "2, 3")) 68 | fmt.Println("test2 3: ", test2(12, "3, 4")) 69 | } 70 | -------------------------------------------------------------------------------- /internal/fakecgo/trampoline_386.s: -------------------------------------------------------------------------------- 1 | /* 2 | See trampoline_amd64.s for explanations. 3 | 4 | => we can use SI, DI, BX instead of the stack 5 | 6 | C Calling convention cdecl used here (we only need integer args): 7 | Pass values on the stack in right to left order 8 | return value will be in AX 9 | 10 | If there are no return values calling conventions are the same -> NOFRAME + JMP 11 | */ 12 | #include "textflag.h" 13 | 14 | TEXT x_cgo_init_trampoline(SB),NOSPLIT|NOFRAME,$0 15 | JMP ·x_cgo_init(SB) 16 | RET 17 | 18 | TEXT x_cgo_thread_start_trampoline(SB),NOSPLIT|NOFRAME,$0 19 | JMP ·x_cgo_thread_start(SB) 20 | RET 21 | 22 | TEXT ·threadentry_trampoline(SB),NOSPLIT,$8 23 | MOVL arg0+0(FP), AX 24 | MOVL AX, 0(SP) 25 | CALL ·threadentry(SB) 26 | MOVL 4(SP), AX 27 | RET 28 | 29 | // func setg_trampoline(setg uintptr, g uintptr) 30 | TEXT ·setg_trampoline(SB),NOSPLIT,$4-8 31 | MOVL g+4(FP), AX 32 | MOVL AX, 0(SP) 33 | MOVL setg+0(FP), AX 34 | CALL AX 35 | RET 36 | 37 | TEXT x_cgo_notify_runtime_init_done_trampoline(SB),NOSPLIT|NOFRAME,$0 38 | JMP ·x_cgo_notify_runtime_init_done(SB) 39 | RET 40 | 41 | TEXT x_cgo_setenv_trampoline(SB),NOSPLIT|NOFRAME,$0 42 | JMP ·x_cgo_setenv(SB) 43 | RET 44 | 45 | TEXT x_cgo_unsetenv_trampoline(SB),NOSPLIT|NOFRAME,$0 46 | JMP ·x_cgo_unsetenv(SB) 47 | RET 48 | 49 | // func asmlibccall6(fn, n, args uintptr) uintptr 50 | TEXT ·asmlibccall6(SB),NOSPLIT,$0-16 51 | MOVL SP, DI 52 | MOVL n+4(FP), AX 53 | MOVL args+8(FP), BX 54 | MOVL fn+0(FP), DX 55 | 56 | TESTL AX, AX 57 | JZ finished 58 | 59 | next: 60 | DECL AX 61 | MOVL (BX)(AX*4), CX 62 | SUBL $4, SP 63 | MOVL CX, 0(SP) 64 | 65 | TESTL AX, AX 66 | JZ finished 67 | JMP next 68 | 69 | finished: 70 | CALL DX 71 | 72 | MOVL DI, SP 73 | 74 | MOVL AX, ret+12(FP) 75 | 76 | RET 77 | -------------------------------------------------------------------------------- /examplelibpcap/main.go: -------------------------------------------------------------------------------- 1 | // Example using libpcap to read packets from a networkdevice and decode them with gopacket. 2 | package main 3 | 4 | import ( 5 | "flag" 6 | "log" 7 | "syscall" 8 | "time" 9 | "unsafe" 10 | 11 | "github.com/google/gopacket" 12 | "github.com/google/gopacket/layers" 13 | "github.com/notti/nocgo" 14 | ) 15 | 16 | type pkthdr struct { 17 | ts syscall.Timeval 18 | caplen uint32 19 | len uint32 20 | } 21 | 22 | func main() { 23 | dev := flag.String("dev", "lo", "The device to listen to") 24 | snaplen := flag.Int("snaplen", 1500, "Maximum capture length") 25 | promisc := flag.Bool("promisc", true, "Set device to promiscous mode") 26 | 27 | flag.Parse() 28 | 29 | p := int32(0) 30 | if *promisc { 31 | p = 1 32 | } 33 | 34 | lib, err := nocgo.Open("libpcap.so") 35 | if err != nil { 36 | log.Fatalln("Couldn't load libpcap: ", err) 37 | } 38 | 39 | var pcapOpenLive func(device []byte, snaplen int32, promisc int32, toMS int32, errbuf []byte) uintptr 40 | if err := lib.Func("pcap_open_live", &pcapOpenLive); err != nil { 41 | log.Fatalln("Couldn't get pcap_open_live: ", err) 42 | } 43 | 44 | var pcapNextEx func(p uintptr, hdr **pkthdr, data *unsafe.Pointer) int32 45 | if err := lib.Func("pcap_next_ex", &pcapNextEx); err != nil { 46 | log.Fatalln("Couldn't load pcap_next_ex: ", err) 47 | } 48 | 49 | errbuf := make([]byte, 512) 50 | pcapHandle := pcapOpenLive(nocgo.MakeCString(*dev), int32(*snaplen), p, 100, errbuf) 51 | 52 | if pcapHandle == 0 { 53 | log.Fatalf("Couldn't open %s: %s\n", *dev, nocgo.MakeGoStringFromSlice(errbuf)) 54 | } 55 | 56 | var hdr *pkthdr 57 | var dataptr unsafe.Pointer 58 | 59 | for { 60 | if ret := pcapNextEx(pcapHandle, &hdr, &dataptr); ret != 1 { 61 | log.Fatalln("Unexpected error code ", ret) 62 | } 63 | 64 | log.Printf("Received packet at %s length %d\n", time.Unix(hdr.ts.Sec, hdr.ts.Usec), hdr.len) 65 | packet := gopacket.NewPacket((*[1 << 30]byte)(dataptr)[:hdr.caplen], layers.LayerTypeEthernet, gopacket.Default) 66 | log.Println(packet.Dump()) 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /internal/fakecgo/trampoline_amd64.s: -------------------------------------------------------------------------------- 1 | /* 2 | trampoline for emulating required C functions for cgo in go (see cgo.go) 3 | (we convert cdecl calling convention to go and vice-versa) 4 | 5 | Since we're called from go and call into C we can cheat a bit with the calling conventions: 6 | - in go all the registers are caller saved 7 | - in C we have a couple of callee saved registers 8 | 9 | => we can use BX, R12, R13, R14, R15 instead of the stack 10 | 11 | C Calling convention cdecl used here (we only need integer args): 12 | 1. arg: DI 13 | 2. arg: SI 14 | 3. arg: DX 15 | 4. arg: CX 16 | 5. arg: R8 17 | 6. arg: R9 18 | We don't need floats with these functions -> AX=0 19 | return value will be in AX 20 | */ 21 | #include "textflag.h" 22 | 23 | TEXT x_cgo_init_trampoline(SB),NOSPLIT,$16 24 | MOVQ DI, 0(SP) 25 | MOVQ SI, 8(SP) 26 | CALL ·x_cgo_init(SB) 27 | RET 28 | 29 | TEXT x_cgo_thread_start_trampoline(SB),NOSPLIT,$8 30 | MOVQ DI, 0(SP) 31 | CALL ·x_cgo_thread_start(SB) 32 | RET 33 | 34 | TEXT ·threadentry_trampoline(SB),NOSPLIT,$16 35 | MOVQ DI, 0(SP) 36 | CALL ·threadentry(SB) 37 | MOVQ 8(SP), AX 38 | RET 39 | 40 | // func setg_trampoline(setg uintptr, g uintptr) 41 | TEXT ·setg_trampoline(SB),NOSPLIT,$0-16 42 | MOVQ g+8(FP), DI 43 | MOVQ setg+0(FP), AX 44 | CALL AX 45 | RET 46 | 47 | TEXT x_cgo_notify_runtime_init_done_trampoline(SB),NOSPLIT,$0 48 | CALL ·x_cgo_notify_runtime_init_done(SB) 49 | RET 50 | 51 | TEXT x_cgo_setenv_trampoline(SB),NOSPLIT,$8 52 | MOVQ DI, 0(SP) 53 | CALL ·x_cgo_setenv(SB) 54 | RET 55 | 56 | TEXT x_cgo_unsetenv_trampoline(SB),NOSPLIT,$8 57 | MOVQ DI, 0(SP) 58 | CALL ·x_cgo_unsetenv(SB) 59 | RET 60 | 61 | // func asmlibccall6(fn, n, args uintptr) uintptr 62 | TEXT ·asmlibccall6(SB),NOSPLIT,$0-32 63 | MOVQ n+8(FP), AX 64 | 65 | TESTQ AX, AX 66 | JZ skipargs 67 | 68 | MOVQ args+16(FP), AX 69 | MOVQ 0(AX), DI 70 | MOVQ 8(AX), SI 71 | MOVQ 16(AX), DX 72 | MOVQ 24(AX), CX 73 | MOVQ 32(AX), R8 74 | MOVQ 40(AX), R9 75 | 76 | skipargs: 77 | 78 | MOVQ fn+0(FP), BX 79 | 80 | MOVQ SP, R12 81 | ANDQ $~0xF, SP // 16 byte alignment for cdecl 82 | 83 | XORQ AX, AX // no fp arguments 84 | CALL BX 85 | 86 | MOVQ R12, SP 87 | 88 | MOVQ AX, ret+24(FP) 89 | 90 | RET 91 | -------------------------------------------------------------------------------- /doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Package nocgo provides a dlopen wrapper that doesn't need cgo. 3 | 4 | WARNING! This also supports cgo! So if you want to ensure no cgo, you have to set the environment variable CGO_ENABLED=0 for the build process. 5 | 6 | Usage 7 | 8 | Don't use this in production! This is meant as a PoC for golang and subject to changes. 9 | 10 | So far only floats, integers, and pointers are supported (No structs, no complex types, and no callbacks, but that wouldn't be hard to implement). 11 | See example, examplelibpcap, and the documentation below for examples. 12 | 13 | Go to C type mappings 14 | 15 | In the type specification struct go types, which will be mapped to C types, have to be used. 16 | 17 | Go type C Type 18 | ========================================================= 19 | int8, byte char 20 | uint8, bool unsigned char 21 | int16 short 22 | uint16 unsigned short 23 | int32 int 24 | uint32 unsigned int 25 | int64 long 26 | uint64 unsigned long 27 | float32 float 28 | float64 double 29 | [], uintptr, reflect.UnsafePointer, * * 30 | 31 | The last line means that slices and pointers are mapped to pointers in C. Pointers to structs are possible, but the structs must follow C alignment rules! 32 | Be carefull with the mappings - there is NO type checking, which is actually impossible since the imported library doesn't know these things. 33 | 34 | go int is not supported to prevent confusion: int sizes in go for 32bit and 64 bit differ, while in C (cdecl) they do not! 35 | 36 | Helperfunctions for converting between C strings and go strings are provided (see below in the function documentation). 37 | 38 | Argument Specifications 39 | 40 | Arguments to functions must be specified via a pointer to func variable. A call to lib.Func will examine arguments and 41 | eventual return value (only one or no return values allowed!), and set the function variable to a wrapper that will call into the desired C-function. 42 | 43 | Example for pcap_open_live (libpcap): 44 | 45 | C declaration: 46 | pcap_t *pcap_open_live(const char *device, int snaplen, int promisc, int to_ms, char *errbuf); 47 | 48 | nocgo declaration: 49 | var pcapOpenLive func(device []byte, snaplen int32, promisc int32, toMS int32, errbuf []byte) uintptr 50 | 51 | ret uses uintptr as type since pcap_t* is just passed to every other libpcap function and we don't care or need to know whats actually in there or where it's pointing to. 52 | */ 53 | package nocgo 54 | -------------------------------------------------------------------------------- /internal/ffi/call_386.s: -------------------------------------------------------------------------------- 1 | #include "textflag.h" 2 | #include "go_asm.h" 3 | 4 | // runtime has #include "go_asm.h" 5 | // we need to fake the defines here: 6 | #define slice_array 0 7 | #define slice_len 4 8 | #define slice_cap 8 9 | 10 | TEXT ·cgocall(SB),NOSPLIT,$0 11 | JMP runtime·cgocall(SB) 12 | 13 | // pass struct { &args, &spec } to cgocall 14 | TEXT ·callWrapper(SB),NOSPLIT|WRAPPER,$16 15 | MOVL DX, 12(SP) 16 | LEAL argframe+0(FP), AX 17 | MOVL AX, 8(SP) 18 | LEAL 8(SP), AX 19 | MOVL AX, 4(SP) 20 | LEAL asmcall(SB), AX 21 | MOVL AX, 0(SP) 22 | CALL ·cgocall(SB) 23 | RET 24 | 25 | TEXT asmcall(SB),NOSPLIT,$0-4 26 | MOVL frame+0(FP), SI // &args, &spec (preserved) 27 | MOVL SP, DI // STACK (preserved) 28 | 29 | MOVL 4(SI), AX // spec 30 | MOVL spec_stack+slice_len(AX), BX 31 | TESTL BX, BX 32 | JZ prepared 33 | 34 | MOVL spec_stack+slice_array(AX), DX 35 | 36 | next: 37 | DECL BX 38 | MOVL (DX)(BX*argument__size), AX 39 | MOVWLZX AX, CX 40 | SHRL $16, AX 41 | 42 | #define TYPE(which, instr) \ 43 | CMPB AX, which \ 44 | JNE 9(PC) \ 45 | SUBL $4, SP \ 46 | MOVL 0(SI), AX \ 47 | ADDL CX, AX \ 48 | instr 0(AX), AX \ 49 | MOVL AX, 0(SP) \ 50 | TESTL BX, BX \ 51 | JZ prepared \ 52 | JMP next 53 | 54 | TYPE($const_type32, MOVL) 55 | TYPE($const_type16, MOVW) 56 | TYPE($const_type8, MOVB) 57 | INT $3 58 | 59 | prepared: 60 | 61 | MOVL 4(SI), AX 62 | CALL spec_fn(AX) 63 | 64 | // return value in AX, DX, F0 <- DON'T USE THESE 65 | 66 | MOVL DI, SP // restore stack 67 | 68 | MOVL 4(SI), DI // DI: spec 69 | 70 | MOVL spec_ret(DI), BX 71 | TESTL BX, BX 72 | JS done 73 | 74 | MOVL 0(SI), SI // SI: args 75 | 76 | // TODO: check SI, if it still points to the correct stack! (could happen if we have a callback into go that splits the stack) 77 | 78 | MOVWLZX BX, CX 79 | SHRL $16, BX 80 | 81 | CMPB BX, $const_type32 82 | JNE 4(PC) 83 | ADDL SI, CX 84 | MOVL AX, (CX) 85 | JMP done 86 | 87 | CMPB BX, $const_type16 88 | JNE 4(PC) 89 | ADDL SI, CX 90 | MOVW AX, (CX) 91 | JMP done 92 | 93 | CMPB BX, $const_type8 94 | JNE 4(PC) 95 | ADDL SI, CX 96 | MOVB AX, (CX) 97 | JMP done 98 | 99 | CMPB BX, $const_typeFloat 100 | JNE 4(PC) 101 | ADDL SI, CX 102 | FMOVF F0, (CX) 103 | JMP done 104 | 105 | CMPB BX, $const_typeDouble 106 | JNE 4(PC) 107 | ADDL SI, CX 108 | FMOVD F0, (CX) 109 | JMP done 110 | 111 | CMPB BX, $const_type64 112 | JNE 6(PC) 113 | ADDL SI, CX 114 | MOVL AX, (CX) 115 | ADDL $4, CX 116 | MOVL DX, (CX) 117 | JMP done 118 | 119 | INT $3 120 | 121 | done: 122 | 123 | RET 124 | -------------------------------------------------------------------------------- /internal/ffi/call_386.go: -------------------------------------------------------------------------------- 1 | package ffi 2 | 3 | import ( 4 | "unsafe" 5 | ) 6 | 7 | // 386 cdecl calling conventions: http://www.sco.com/developers/devspecs/abi386-4.pdf 8 | // Pass everything on the stack in right to left order 9 | // Return is in AX (and DX for 64 bit) or F0 for floats 10 | // according to libffi clang might require the caller to properly (sign)extend stuff - so we do that 11 | // structs are not supported for now (neither as argument nor as return value) - but this is not hard to do 12 | 13 | type argtype uint16 14 | 15 | const ( 16 | type32 argtype = 0 // movl 64 bit 17 | type16 argtype = 1 // movw 16 bit 18 | type8 argtype = 2 // movb 8 bit 19 | typeDouble argtype = 3 // fld 64 bit (only return) 20 | typeFloat argtype = 4 // movss 32 bit (only return) 21 | type64 argtype = 5 // 2x movl (only return) 22 | typeUnused argtype = 0xFFFF 23 | ) 24 | 25 | type argument struct { 26 | offset uint16 27 | t argtype 28 | } 29 | 30 | // spec a wrapper specifcation with instructions on how to place arguments into registers/stack 31 | type spec struct { 32 | wrapper uintptr // pointer to callWrapper() 33 | fn uintptr // pointer to the C-function 34 | stack []argument 35 | ret argument 36 | } 37 | 38 | func callWrapper() 39 | 40 | // MakeSpec builds a call specification for the given arguments 41 | func MakeSpec(fn uintptr, fun interface{}) error { 42 | fptr, arguments, ret, err := stackFields(fun) 43 | if err != nil { 44 | return err 45 | } 46 | 47 | spec := new(spec) 48 | spec.wrapper = funcPC(callWrapper) 49 | spec.fn = fn 50 | spec.ret.t = typeUnused 51 | 52 | // on 386 we can't directly pass the arguments to the function :( 53 | // -> go aligns arguments like a struct and 386 aligns every argument to 4 byte boundaries 54 | for _, arg := range arguments { 55 | switch arg.size { 56 | case 8: 57 | spec.stack = append(spec.stack, argument{uint16(arg.offset), type32}) 58 | spec.stack = append(spec.stack, argument{uint16(arg.offset + 4), type32}) 59 | case 4: 60 | spec.stack = append(spec.stack, argument{uint16(arg.offset), type32}) 61 | case 2: 62 | spec.stack = append(spec.stack, argument{uint16(arg.offset), type16}) 63 | case 1: 64 | spec.stack = append(spec.stack, argument{uint16(arg.offset), type8}) 65 | } 66 | } 67 | 68 | if ret.c != classVoid { 69 | var t argtype 70 | switch ret.c { 71 | case classInt, classUint: 72 | switch ret.size { 73 | case 8: 74 | t = type64 75 | case 4: 76 | t = type32 77 | case 2: 78 | t = type16 79 | case 1: 80 | t = type8 81 | } 82 | case classFloat: 83 | switch ret.size { 84 | case 8: 85 | t = typeDouble 86 | case 4: 87 | t = typeFloat 88 | } 89 | } 90 | 91 | spec.ret.t = t 92 | spec.ret.offset = uint16(ret.offset) 93 | } 94 | 95 | *(*unsafe.Pointer)(fptr) = unsafe.Pointer(spec) 96 | 97 | return nil 98 | } 99 | -------------------------------------------------------------------------------- /dlopen.go: -------------------------------------------------------------------------------- 1 | package nocgo 2 | 3 | import ( 4 | "errors" 5 | "reflect" 6 | "unsafe" 7 | 8 | "github.com/notti/nocgo/internal/dlopen" 9 | "github.com/notti/nocgo/internal/ffi" 10 | ) 11 | 12 | func getLastError() error { 13 | err := dlopen.DLError() 14 | if err == 0 { 15 | return errors.New("Unknown dl error") 16 | } 17 | return errors.New(MakeGoStringFromPointer(err)) 18 | } 19 | 20 | // Library holds the handle to a loaded library 21 | type Library uintptr 22 | 23 | // Open opens the given dynamic library and returns a handle for loading symbols and functions. 24 | func Open(library string) (Library, error) { 25 | handle := dlopen.DLOpen(MakeCString(library), 2 /* RTLD_NOW */) 26 | if handle != 0 { 27 | return Library(handle), nil 28 | } 29 | return 0, getLastError() 30 | } 31 | 32 | // Close closes the library. This might also release all resources. Any Func and Value calls on the Library after this point can give unexpected results. 33 | func (l Library) Close() error { 34 | ret := dlopen.DLClose(uintptr(l)) 35 | if ret == 0 { 36 | return nil 37 | } 38 | return getLastError() 39 | } 40 | 41 | // Func returns a callable spec for the given symbol name and argument specification. 42 | // 43 | // WARNING! This does not and cannot check if the size of the given type is correct! 44 | // 45 | // Example: 46 | // var puts func(s []byte) int32 47 | // if err := lib.Func("puts", &puts); err != nil { 48 | // //handle error; err will contain an error message from dlerror, or if something went wrong with building the spec 49 | // } 50 | // num := puts(nocgo.MakeCString("hello world!\n")) 51 | // fmt.Printf("Successfully printed %d characters from C!\n", num) 52 | // 53 | // See package documentation for an explanation of C-types 54 | func (l Library) Func(name string, fun interface{}) error { 55 | addr := dlopen.DLSym(uintptr(l), MakeCString(name)) 56 | if addr == 0 { 57 | return getLastError() 58 | } 59 | return ffi.MakeSpec(addr, fun) 60 | } 61 | 62 | // Value sets the given value (which must be pointer to pointer to the correct type) to the global symbol given by name. 63 | // 64 | // WARNING! This does not and cannot check if the size of the given type is correct! This might be possibly dangerous. 65 | // See above for an explanation of C-types. 66 | // 67 | // Example: 68 | // var value *int32 69 | // if err := lib.Value("some_value", &value); err != nil { 70 | // //handle error; error will contain an error message from dlerror 71 | // } 72 | // 73 | // // *value now is the contents of the global symbol in the library 74 | // fmt.Printf(*value) 75 | func (l Library) Value(name string, value interface{}) error { 76 | v := reflect.ValueOf(value) 77 | if v.Kind() != reflect.Ptr { 78 | return errors.New("value must be a pointer to a pointer") 79 | } 80 | v = v.Elem() 81 | if v.Kind() != reflect.Ptr { 82 | return errors.New("value must be pointer to a pointer") 83 | } 84 | 85 | addr := dlopen.DLSym(uintptr(l), MakeCString(name)) 86 | if addr == 0 { 87 | return getLastError() 88 | } 89 | 90 | *(*uintptr)(unsafe.Pointer(v.UnsafeAddr())) = addr 91 | 92 | return nil 93 | } 94 | -------------------------------------------------------------------------------- /internal/fakecgo/libcdefs.go: -------------------------------------------------------------------------------- 1 | package fakecgo 2 | 3 | import ( 4 | "reflect" 5 | "unsafe" 6 | ) 7 | 8 | // Wrapper functions to provide libc-functions for cgo.go 9 | 10 | // C-types (linux glibc, libc freebsd) we don't need to see what's inside pthread_attr and sigset_t -> byte arrays: 11 | type pthread_attr [56]byte // this is 36 on 386 glibc, 16 on amd64 freebsd - but too big doesn't hurt 12 | type sigset_t [128]byte // this is 16 on amd64 freebsd - but too big doesn't hurt 13 | type size_t int 14 | type pthread_t int 15 | type timespec struct { 16 | tv_sec int 17 | tv_nsec int 18 | } 19 | 20 | // We could take timespec from syscall - but there it uses int32 and int64 for 32 bit and 64 bit arch, which complicates stuff for us 21 | 22 | // for pthread_sigmask: 23 | 24 | type sighow int32 25 | 26 | const ( 27 | SIG_BLOCK sighow = 0 28 | SIG_UNBLOCK sighow = 1 29 | SIG_SETMASK sighow = 2 30 | ) 31 | 32 | // Every wrapper here MUST NOT split stack or have write barriers! (see cgoGlue.go) 33 | 34 | //go:nosplit 35 | func pthread_attr_init(attr *pthread_attr) int32 { 36 | return int32(libcCall1(libc_pthread_attr_init, uintptr(unsafe.Pointer(attr)))) 37 | } 38 | 39 | //go:nosplit 40 | func pthread_attr_getstacksize(attr *pthread_attr, stacksize *size_t) int32 { 41 | return int32(libcCall2(libc_pthread_attr_getstacksize, uintptr(unsafe.Pointer(attr)), uintptr(unsafe.Pointer(stacksize)))) 42 | } 43 | 44 | //go:nosplit 45 | func pthread_attr_destroy(attr *pthread_attr) int32 { 46 | return int32(libcCall1(libc_pthread_attr_destroy, uintptr(unsafe.Pointer(attr)))) 47 | } 48 | 49 | //go:nosplit 50 | func pthread_sigmask(how sighow, set, oldset *sigset_t) int32 { 51 | return int32(libcCall3(libc_pthread_sigmask, uintptr(how), uintptr(unsafe.Pointer(set)), uintptr(unsafe.Pointer(oldset)))) 52 | } 53 | 54 | //go:nosplit 55 | func pthread_create(thread *pthread_t, attr *pthread_attr, start, arg unsafe.Pointer) int32 { 56 | return int32(libcCall4(libc_pthread_create, uintptr(unsafe.Pointer(thread)), uintptr(unsafe.Pointer(attr)), uintptr(start), uintptr(arg))) 57 | } 58 | 59 | //go:nosplit 60 | func pthread_detach(thread pthread_t) int32 { 61 | return int32(libcCall1(libc_pthread_detach, uintptr(thread))) 62 | } 63 | 64 | //go:nosplit 65 | func setenv(name, value *byte, overwrite int32) int32 { 66 | return int32(libcCall3(libc_setenv, uintptr(unsafe.Pointer(name)), uintptr(unsafe.Pointer(value)), uintptr(overwrite))) 67 | } 68 | 69 | //go:nosplit 70 | func unsetenv(name *byte) int32 { 71 | return int32(libcCall1(libc_unsetenv, uintptr(unsafe.Pointer(name)))) 72 | } 73 | 74 | //go:nosplit 75 | func malloc(size uintptr) uintptr { 76 | return libcCall1(libc_malloc, uintptr(size)) 77 | } 78 | 79 | //go:nosplit 80 | func free(ptr unsafe.Pointer) { 81 | libcCall1(libc_free, uintptr(ptr)) 82 | } 83 | 84 | //go:nosplit 85 | func nanosleep(rgtp, rmtp *timespec) int32 { 86 | return int32(libcCall2(libc_nanosleep, uintptr(unsafe.Pointer(rgtp)), uintptr(unsafe.Pointer(rmtp)))) 87 | } 88 | 89 | //go:nosplit 90 | func sigfillset(set *sigset_t) int32 { 91 | return int32(libcCall1(libc_sigfillset, uintptr(unsafe.Pointer(set)))) 92 | } 93 | 94 | //go:nosplit 95 | func abort() { 96 | libcCall0(libc_abort) 97 | } 98 | 99 | //go:nosplit 100 | func dprintf(fd uintptr, fmt string, arg ...uintptr) int32 { 101 | var args [4]uintptr 102 | copy(args[:], arg) 103 | return int32(libcCall6(libc_dprintf, fd, (*reflect.StringHeader)(unsafe.Pointer(&fmt)).Data, args[0], args[1], args[2], args[3])) 104 | } 105 | 106 | //go:nosplit 107 | func strerror(err int32) uintptr { 108 | return libcCall1(libc_strerror, uintptr(err)) 109 | } 110 | -------------------------------------------------------------------------------- /internal/ffi/ffi.go: -------------------------------------------------------------------------------- 1 | package ffi 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "reflect" 7 | "unsafe" 8 | ) 9 | 10 | //go:linkname funcPC runtime.funcPC 11 | func funcPC(f interface{}) uintptr 12 | 13 | //go:linkname cgocall runtime.cgocall 14 | func cgocall(fn, arg unsafe.Pointer) int32 15 | 16 | //go:linkname _Cgo_always_false runtime.cgoAlwaysFalse 17 | var _Cgo_always_false bool 18 | 19 | //go:linkname _Cgo_use runtime.cgoUse 20 | func _Cgo_use(interface{}) 21 | 22 | type class int 23 | 24 | const ( 25 | classVoid = iota 26 | classInt 27 | classUint 28 | classFloat 29 | ) 30 | 31 | type value struct { 32 | offset int 33 | c class 34 | size int 35 | align int 36 | } 37 | 38 | // stackFields takes pointer to function variable and returns: pointer to set it, argument offsets and type, and return value and type 39 | // Arguments in go are according to the following (from cmd/compile/internal/gc/align.go dowidth TFUNCARGS): 40 | // 3 consecutive structures on the stack 41 | // 1. struct: receiver argument(s) 42 | // 2. struct (aligned to register width): parameters 43 | // 3. struct (aligned to register width): return values 44 | func stackFields(fun interface{}) (fptr unsafe.Pointer, arguments []value, ret value, err error) { 45 | v := reflect.ValueOf(fun) 46 | if v.Kind() != reflect.Ptr { 47 | err = errors.New("provided argument must be pointer to function variable") 48 | return 49 | } 50 | f := v.Elem().Type() 51 | if f.Kind() != reflect.Func { 52 | err = errors.New("provided argument must be pointer to function variable") 53 | return 54 | } 55 | if f.NumOut() > 1 { 56 | err = errors.New("only one or no return argument allowed") 57 | return 58 | } 59 | 60 | ret.c = classVoid 61 | 62 | offset := 0 63 | 64 | for i := 0; i < f.NumIn(); i++ { 65 | a := f.In(i) 66 | k := a.Kind() 67 | var v value 68 | 69 | skip := 0 70 | 71 | v.size = int(a.Size()) 72 | v.align = a.Align() 73 | 74 | switch k { 75 | case reflect.Slice: 76 | v.size = int(unsafe.Sizeof(uintptr(0))) 77 | skip = int(unsafe.Sizeof(reflect.SliceHeader{})) - v.size 78 | v.c = classUint 79 | case reflect.Uintptr, reflect.Ptr, reflect.UnsafePointer, reflect.Uint64, reflect.Uint32, reflect.Uint16, reflect.Uint8, reflect.Bool: 80 | v.c = classUint 81 | case reflect.Int64, reflect.Int32, reflect.Int16, reflect.Int8: 82 | v.c = classInt 83 | case reflect.Float32, reflect.Float64: 84 | v.c = classFloat 85 | default: 86 | err = fmt.Errorf("type %s of argument number %d not supported", k, i) 87 | return 88 | } 89 | 90 | offset = (offset + v.align - 1) &^ (v.align - 1) 91 | v.offset = offset 92 | 93 | arguments = append(arguments, v) 94 | 95 | offset += skip + v.size 96 | } 97 | 98 | if f.NumOut() == 1 { 99 | a := f.Out(0) 100 | k := a.Kind() 101 | 102 | ret.size = int(a.Size()) 103 | ret.align = int(unsafe.Sizeof(uintptr(0))) // return values are aligned by register size - let's hope this is the same as the pointer size 104 | 105 | switch k { 106 | case reflect.Slice: 107 | ret.size = int(unsafe.Sizeof(uintptr(0))) 108 | ret.c = classUint 109 | case reflect.Uintptr, reflect.Ptr, reflect.UnsafePointer, reflect.Uint64, reflect.Uint32, reflect.Uint16, reflect.Uint8, reflect.Bool: 110 | ret.c = classUint 111 | case reflect.Int64, reflect.Int32, reflect.Int16, reflect.Int8: 112 | ret.c = classInt 113 | case reflect.Float32, reflect.Float64: 114 | ret.c = classFloat 115 | default: 116 | err = fmt.Errorf("type %s of return value not supported", k) 117 | return 118 | } 119 | 120 | offset = (offset + ret.align - 1) &^ (ret.align - 1) 121 | ret.offset = offset 122 | } 123 | 124 | fptr = unsafe.Pointer(v.Pointer()) 125 | 126 | return 127 | } 128 | -------------------------------------------------------------------------------- /internal/fakecgo/symbols.go: -------------------------------------------------------------------------------- 1 | package fakecgo 2 | 3 | import _ "unsafe" //needed for go:linkname 4 | 5 | // The following "fakes" all the necessary stuff for pretending we're using cgo, without actually doing that 6 | // -> iscgo will be set to true and all functions that are then required by the runtime implemented 7 | // This is necessary to get TLS working in the mainthread (cgo_init) and in all other threads (cgo_thread_start). 8 | // If we leave this out, libc can't use TLS since go runtime overwrites it (printf with %f already needs that) 9 | 10 | // The actual functions are implemented in assembly trampolines (trampoline_*.s) that call into carefully crafted golang (cgo.go) 11 | 12 | //go:linkname _cgo_init _cgo_init 13 | //go:linkname x_cgo_init_trampoline x_cgo_init_trampoline 14 | var x_cgo_init_trampoline byte 15 | var _cgo_init = &x_cgo_init_trampoline 16 | 17 | //go:linkname x_cgo_thread_start_trampoline x_cgo_thread_start_trampoline 18 | //go:linkname _cgo_thread_start _cgo_thread_start 19 | var x_cgo_thread_start_trampoline byte 20 | var _cgo_thread_start = &x_cgo_thread_start_trampoline 21 | 22 | //go:linkname x_cgo_notify_runtime_init_done_trampoline x_cgo_notify_runtime_init_done_trampoline 23 | //go:linkname _cgo_notify_runtime_init_done _cgo_notify_runtime_init_done 24 | var x_cgo_notify_runtime_init_done_trampoline byte 25 | var _cgo_notify_runtime_init_done = &x_cgo_notify_runtime_init_done_trampoline 26 | 27 | //go:linkname x_cgo_setenv_trampoline x_cgo_setenv_trampoline 28 | //go:linkname _cgo_setenv runtime._cgo_setenv 29 | var x_cgo_setenv_trampoline byte 30 | var _cgo_setenv = &x_cgo_setenv_trampoline 31 | 32 | //go:linkname x_cgo_unsetenv_trampoline x_cgo_unsetenv_trampoline 33 | //go:linkname _cgo_unsetenv runtime._cgo_unsetenv 34 | var x_cgo_unsetenv_trampoline byte 35 | var _cgo_unsetenv = &x_cgo_unsetenv_trampoline 36 | 37 | // let's pretend we have cgo: 38 | //go:linkname _iscgo runtime.iscgo 39 | var _iscgo = true 40 | 41 | // Now all the symbols we need to import from various libraries to implement the above functions: 42 | // (just using one variable and taking the address in libcFuncs.go works with amd64 - but the two variable dance is needed for 386, where we get an unknown symbol relocation otherwise :/) 43 | 44 | //go:linkname libc_pthread_attr_init_x libc_pthread_attr_init_x 45 | var libc_pthread_attr_init_x libcFunc 46 | var libc_pthread_attr_init = &libc_pthread_attr_init_x 47 | 48 | //go:linkname libc_pthread_attr_getstacksize_x libc_pthread_attr_getstacksize_x 49 | var libc_pthread_attr_getstacksize_x libcFunc 50 | var libc_pthread_attr_getstacksize = &libc_pthread_attr_getstacksize_x 51 | 52 | //go:linkname libc_pthread_attr_destroy_x libc_pthread_attr_destroy_x 53 | var libc_pthread_attr_destroy_x libcFunc 54 | var libc_pthread_attr_destroy = &libc_pthread_attr_destroy_x 55 | 56 | //go:linkname libc_pthread_sigmask_x libc_pthread_sigmask_x 57 | var libc_pthread_sigmask_x libcFunc 58 | var libc_pthread_sigmask = &libc_pthread_sigmask_x 59 | 60 | //go:linkname libc_pthread_create_x libc_pthread_create_x 61 | var libc_pthread_create_x libcFunc 62 | var libc_pthread_create = &libc_pthread_create_x 63 | 64 | //go:linkname libc_pthread_detach_x libc_pthread_detach_x 65 | var libc_pthread_detach_x libcFunc 66 | var libc_pthread_detach = &libc_pthread_detach_x 67 | 68 | //go:linkname libc_setenv_x libc_setenv_x 69 | var libc_setenv_x libcFunc 70 | var libc_setenv = &libc_setenv_x 71 | 72 | //go:linkname libc_unsetenv_x libc_unsetenv_x 73 | var libc_unsetenv_x libcFunc 74 | var libc_unsetenv = &libc_unsetenv_x 75 | 76 | //go:linkname libc_malloc_x libc_malloc_x 77 | var libc_malloc_x libcFunc 78 | var libc_malloc = &libc_malloc_x 79 | 80 | //go:linkname libc_free_x libc_free_x 81 | var libc_free_x libcFunc 82 | var libc_free = &libc_free_x 83 | 84 | //go:linkname libc_nanosleep_x libc_nanosleep_x 85 | var libc_nanosleep_x libcFunc 86 | var libc_nanosleep = &libc_nanosleep_x 87 | 88 | //go:linkname libc_sigfillset_x libc_sigfillset_x 89 | var libc_sigfillset_x libcFunc 90 | var libc_sigfillset = &libc_sigfillset_x 91 | 92 | //go:linkname libc_abort_x libc_abort_x 93 | var libc_abort_x libcFunc 94 | var libc_abort = &libc_abort_x 95 | 96 | //go:linkname libc_dprintf_x libc_dprintf_x 97 | var libc_dprintf_x libcFunc 98 | var libc_dprintf = &libc_dprintf_x 99 | 100 | //go:linkname libc_strerror_x libc_strerror_x 101 | var libc_strerror_x libcFunc 102 | var libc_strerror = &libc_strerror_x 103 | -------------------------------------------------------------------------------- /internal/ffi/call_amd64.go: -------------------------------------------------------------------------------- 1 | package ffi 2 | 3 | import ( 4 | "unsafe" 5 | ) 6 | 7 | // amd64 cdecl calling conventions: https://www.uclibc.org/docs/psABI-x86_64.pdf 8 | // - Align the stack (should be 32byte aligned before the function is called - 16 byte is enough if we don't pass 256bit integers) 9 | // - Pass first integer arguments in DI, SI, DX, CX, R8, R9 10 | // - Pass first float arguments in X0-X7 11 | // - Pass rest on the stack 12 | // - Pass number of used float registers in AX 13 | // Return is in AX or X0 for floats 14 | // according to libffi clang might require the caller to properly (sign)extend stuff in registers - so we do that 15 | // structs are not supported for now (neither as argument nor as return value) - but this is not hard to do 16 | 17 | type argtype uint16 18 | 19 | const ( 20 | type64 argtype = 0 // movq 64 bit 21 | typeS32 argtype = 1 // movlqsx signed 32 bit 22 | typeU32 argtype = 2 // movlqzx unsigned 32 bit 23 | typeS16 argtype = 3 // movwqsx signed 16 bit 24 | typeU16 argtype = 4 // movwqzx unsigned 16 bit 25 | typeS8 argtype = 5 // movbqsx signed 8 bit 26 | typeU8 argtype = 6 // movbqzx unsigned 8 bit 27 | typeDouble argtype = 7 // movsd 64 bit 28 | typeFloat argtype = 8 // movss 32 bit 29 | typeUnused argtype = 0xFFFF 30 | ) 31 | 32 | type argument struct { 33 | offset uint16 34 | t argtype 35 | } 36 | 37 | // spec a wrapper specifcation with instructions on how to place arguments into registers/stack 38 | type spec struct { 39 | wrapper uintptr // pointer to callWrapper() 40 | fn uintptr // pointer to the C-function 41 | stack []argument 42 | intargs [6]argument 43 | xmmargs [8]argument 44 | ret argument 45 | rax uint8 46 | } 47 | 48 | // FIXME: we don't support stuff > 64 bit 49 | 50 | func callWrapper() 51 | 52 | // MakeSpec builds a call specification for the given arguments 53 | func MakeSpec(fn uintptr, fun interface{}) error { 54 | fptr, arguments, ret, err := stackFields(fun) 55 | if err != nil { 56 | return err 57 | } 58 | 59 | spec := new(spec) 60 | spec.wrapper = funcPC(callWrapper) 61 | spec.fn = fn 62 | spec.ret.t = typeUnused 63 | 64 | intreg := 0 65 | xmmreg := 0 66 | 67 | for _, arg := range arguments { 68 | var t argtype 69 | switch arg.c { 70 | case classInt, classUint: 71 | switch { 72 | case arg.size == 8: 73 | t = type64 74 | case arg.size == 4: 75 | if arg.c == classInt { 76 | t = typeS32 77 | } else { 78 | t = typeU32 79 | } 80 | case arg.size == 2: 81 | if arg.c == classInt { 82 | t = typeS16 83 | } else { 84 | t = typeU16 85 | } 86 | case arg.size == 1: 87 | if arg.c == classInt { 88 | t = typeS8 89 | } else { 90 | t = typeU8 91 | } 92 | } 93 | if intreg < 6 { 94 | spec.intargs[intreg] = argument{uint16(arg.offset), t} 95 | intreg++ 96 | } else { 97 | switch t { 98 | case typeS32: 99 | t = typeU32 100 | case typeS16: 101 | t = typeU16 102 | case typeS8: 103 | t = typeU8 104 | } 105 | spec.stack = append(spec.stack, argument{uint16(arg.offset), t}) 106 | } 107 | case classFloat: 108 | switch { 109 | case arg.size == 8: 110 | t = typeDouble 111 | case arg.size == 4: 112 | t = typeFloat 113 | } 114 | if xmmreg < 8 { 115 | spec.xmmargs[xmmreg] = argument{uint16(arg.offset), t} 116 | xmmreg++ 117 | } else { 118 | switch t { 119 | case typeDouble: 120 | t = type64 121 | case typeFloat: 122 | t = typeU32 123 | } 124 | spec.stack = append(spec.stack, argument{uint16(arg.offset), t}) 125 | } 126 | } 127 | } 128 | 129 | spec.rax = uint8(xmmreg) 130 | for i := intreg; i < 6; i++ { 131 | spec.intargs[i].t = typeUnused 132 | } 133 | for i := xmmreg; i < 8; i++ { 134 | spec.xmmargs[i].t = typeUnused 135 | } 136 | 137 | if ret.c != classVoid { 138 | var t argtype 139 | switch ret.c { 140 | case classInt, classUint: 141 | switch ret.size { 142 | case 8: 143 | t = type64 144 | case 4: 145 | t = typeU32 146 | case 2: 147 | t = typeU16 148 | case 1: 149 | t = typeU8 150 | } 151 | case classFloat: 152 | switch ret.size { 153 | case 8: 154 | t = typeDouble 155 | case 4: 156 | t = typeFloat 157 | } 158 | } 159 | spec.ret.t = t 160 | spec.ret.offset = uint16(ret.offset) 161 | } 162 | 163 | *(*unsafe.Pointer)(fptr) = unsafe.Pointer(spec) 164 | 165 | return nil 166 | } 167 | -------------------------------------------------------------------------------- /steps/2_go/test/_obj/main.cgo2.c: -------------------------------------------------------------------------------- 1 | 2 | #line 1 "cgo-builtin-prolog" 3 | #include /* for ptrdiff_t and size_t below */ 4 | 5 | /* Define intgo when compiling with GCC. */ 6 | typedef ptrdiff_t intgo; 7 | 8 | typedef struct { const char *p; intgo n; } _GoString_; 9 | typedef struct { char *p; intgo n; intgo c; } _GoBytes_; 10 | _GoString_ GoString(char *p); 11 | _GoString_ GoStringN(char *p, int l); 12 | _GoBytes_ GoBytes(void *p, int n); 13 | char *CString(_GoString_); 14 | void *CBytes(_GoBytes_); 15 | void *_CMalloc(size_t); 16 | 17 | __attribute__ ((unused)) 18 | static size_t _GoStringLen(_GoString_ s) { return s.n; } 19 | 20 | __attribute__ ((unused)) 21 | static const char *_GoStringPtr(_GoString_ s) { return s.p; } 22 | 23 | #line 3 "/home/notti/go/src/github.com/notti/go-dynamic/steps/2_go/test/main.go" 24 | #include 25 | #include 26 | #include 27 | 28 | #line 1 "cgo-generated-wrapper" 29 | 30 | 31 | #line 1 "cgo-gcc-prolog" 32 | /* 33 | If x and y are not equal, the type will be invalid 34 | (have a negative array count) and an inscrutable error will come 35 | out of the compiler and hopefully mention "name". 36 | */ 37 | #define __cgo_compile_assert_eq(x, y, name) typedef char name[(x-y)*(x-y)*-2+1]; 38 | 39 | /* Check at compile time that the sizes we use match our expectations. */ 40 | #define __cgo_size_assert(t, n) __cgo_compile_assert_eq(sizeof(t), n, _cgo_sizeof_##t##_is_not_##n) 41 | 42 | __cgo_size_assert(char, 1) 43 | __cgo_size_assert(short, 2) 44 | __cgo_size_assert(int, 4) 45 | typedef long long __cgo_long_long; 46 | __cgo_size_assert(__cgo_long_long, 8) 47 | __cgo_size_assert(float, 4) 48 | __cgo_size_assert(double, 8) 49 | 50 | extern char* _cgo_topofstack(void); 51 | 52 | #include 53 | #include 54 | 55 | 56 | #define CGO_NO_SANITIZE_THREAD 57 | #define _cgo_tsan_acquire() 58 | #define _cgo_tsan_release() 59 | 60 | 61 | #define _cgo_msan_write(addr, sz) 62 | 63 | CGO_NO_SANITIZE_THREAD 64 | void 65 | _cgo_194c66e4342c_Cfunc_fputs(void *v) 66 | { 67 | struct { 68 | char const* p0; 69 | FILE* p1; 70 | int r; 71 | char __pad20[4]; 72 | } __attribute__((__packed__, __gcc_struct__)) *_cgo_a = v; 73 | char *_cgo_stktop = _cgo_topofstack(); 74 | __typeof__(_cgo_a->r) _cgo_r; 75 | _cgo_tsan_acquire(); 76 | _cgo_r = fputs(_cgo_a->p0, _cgo_a->p1); 77 | _cgo_tsan_release(); 78 | _cgo_a = (void*)((char*)_cgo_a + (_cgo_topofstack() - _cgo_stktop)); 79 | _cgo_a->r = _cgo_r; 80 | _cgo_msan_write(&_cgo_a->r, sizeof(_cgo_a->r)); 81 | } 82 | 83 | CGO_NO_SANITIZE_THREAD 84 | void 85 | _cgo_194c66e4342c_Cfunc_putc(void *v) 86 | { 87 | struct { 88 | int p0; 89 | char __pad4[4]; 90 | FILE* p1; 91 | int r; 92 | char __pad20[4]; 93 | } __attribute__((__packed__, __gcc_struct__)) *_cgo_a = v; 94 | char *_cgo_stktop = _cgo_topofstack(); 95 | __typeof__(_cgo_a->r) _cgo_r; 96 | _cgo_tsan_acquire(); 97 | _cgo_r = putc(_cgo_a->p0, _cgo_a->p1); 98 | _cgo_tsan_release(); 99 | _cgo_a = (void*)((char*)_cgo_a + (_cgo_topofstack() - _cgo_stktop)); 100 | _cgo_a->r = _cgo_r; 101 | _cgo_msan_write(&_cgo_a->r, sizeof(_cgo_a->r)); 102 | } 103 | 104 | CGO_NO_SANITIZE_THREAD 105 | void 106 | _cgo_194c66e4342c_Cfunc_puts(void *v) 107 | { 108 | struct { 109 | char const* p0; 110 | int r; 111 | char __pad12[4]; 112 | } __attribute__((__packed__, __gcc_struct__)) *_cgo_a = v; 113 | char *_cgo_stktop = _cgo_topofstack(); 114 | __typeof__(_cgo_a->r) _cgo_r; 115 | _cgo_tsan_acquire(); 116 | _cgo_r = puts(_cgo_a->p0); 117 | _cgo_tsan_release(); 118 | _cgo_a = (void*)((char*)_cgo_a + (_cgo_topofstack() - _cgo_stktop)); 119 | _cgo_a->r = _cgo_r; 120 | _cgo_msan_write(&_cgo_a->r, sizeof(_cgo_a->r)); 121 | } 122 | 123 | CGO_NO_SANITIZE_THREAD 124 | void 125 | _cgo_194c66e4342c_Cmacro_stdout(void *v) 126 | { 127 | struct { 128 | FILE* r; 129 | } __attribute__((__packed__, __gcc_struct__)) *_cgo_a = v; 130 | char *_cgo_stktop = _cgo_topofstack(); 131 | __typeof__(_cgo_a->r) _cgo_r; 132 | _cgo_tsan_acquire(); 133 | _cgo_r = (__typeof__(_cgo_a->r)) stdout; 134 | _cgo_tsan_release(); 135 | _cgo_a = (void*)((char*)_cgo_a + (_cgo_topofstack() - _cgo_stktop)); 136 | _cgo_a->r = _cgo_r; 137 | _cgo_msan_write(&_cgo_a->r, sizeof(_cgo_a->r)); 138 | } 139 | 140 | CGO_NO_SANITIZE_THREAD 141 | void 142 | _cgo_194c66e4342c_Cfunc_strcat(void *v) 143 | { 144 | struct { 145 | char* p0; 146 | char const* p1; 147 | char* r; 148 | } __attribute__((__packed__, __gcc_struct__)) *_cgo_a = v; 149 | char *_cgo_stktop = _cgo_topofstack(); 150 | __typeof__(_cgo_a->r) _cgo_r; 151 | _cgo_tsan_acquire(); 152 | _cgo_r = (__typeof__(_cgo_a->r)) strcat(_cgo_a->p0, _cgo_a->p1); 153 | _cgo_tsan_release(); 154 | _cgo_a = (void*)((char*)_cgo_a + (_cgo_topofstack() - _cgo_stktop)); 155 | _cgo_a->r = _cgo_r; 156 | _cgo_msan_write(&_cgo_a->r, sizeof(_cgo_a->r)); 157 | } 158 | 159 | -------------------------------------------------------------------------------- /internal/fakecgo/cgo.go: -------------------------------------------------------------------------------- 1 | /* 2 | Package fakecgo fakes necessary functionality to support calling C-code (TLS initialization for main thread and subsequent threads). 3 | 4 | Usage 5 | 6 | Just import this library with 7 | import _ "github.com/notti/nocgo/fakecgo" 8 | and enjoy the side effects (e.g., you can't use cgo any longer) :) 9 | */ 10 | package fakecgo 11 | 12 | import ( 13 | "syscall" 14 | "unsafe" 15 | ) 16 | 17 | // WARNING: please read this before changing/improving anything 18 | // This here might look like (ugly) go - but it is actually somehow C-code written in go (basically stuff in runtime/cgo/) 19 | // Yeah this somehow works, but needs the trampolines from trampoline_*.s to fix calling conventions cdecl <-> go 20 | // 21 | // Beware that strings must end with a 0 byte to not confuse C-code we call 22 | // 23 | // Write barriers (we will be called while go is in a state where this is not possible) and stack split (we will be on systemstack anyway) are NOT allowed in here 24 | // -> e.g. use memmove for copying to pointers 25 | // go:nowritebarrierrec is only allowed inside runtime - so this has to be checked manually :( 26 | 27 | // pthread_create will call this with ts as argument in a new thread -> this fixes up arguments to go (in assembly) and calls threadentry 28 | func threadentry_trampoline() 29 | 30 | // here we will store a pointer to the provided setg func 31 | var setg_func uintptr 32 | 33 | // x_cgo_init(G *g, void (*setg)(void*)) (runtime/cgo/gcc_linux_amd64.c) 34 | // This get's called during startup, adjusts stacklo, and provides a pointer to setg_gcc for us 35 | // Additionally, if we set _cgo_init to non-null, go won't do it's own TLS setup 36 | // This function can't be go:systemstack since go is not in a state where the systemcheck would work. 37 | // 38 | //go:nosplit 39 | func x_cgo_init(g *g, setg uintptr) { 40 | var size size_t 41 | var attr pthread_attr 42 | 43 | // we need an extra variable here - otherwise go generates "safe" code, which is not allowed here 44 | stackSp := uintptr(unsafe.Pointer(&size)) 45 | 46 | setg_func = setg 47 | 48 | pthread_attr_init(&attr) 49 | pthread_attr_getstacksize(&attr, &size) 50 | g.stack.lo = stackSp - uintptr(size) + 4096 51 | pthread_attr_destroy(&attr) 52 | } 53 | 54 | // _cgo_thread_start is split into three parts in cgo since only one part is system dependent (keep it here for easier handling) 55 | 56 | // _cgo_thread_start(ThreadStart *arg) (runtime/cgo/gcc_util.c) 57 | // This get's called instead of the go code for creating new threads 58 | // -> pthread_* stuff is used, so threads are setup correctly for C 59 | // If this is missing, TLS is only setup correctly on thread 1! 60 | // This function should be go:systemstack instead of go:nosplit (but that requires runtime) 61 | // 62 | //go:nosplit 63 | func x_cgo_thread_start(arg *threadstart) { 64 | ts := malloc(unsafe.Sizeof(threadstart{})) 65 | 66 | if ts == 0 { 67 | dprintf(2, "couldn't allocate memory for threadstart\n\000") 68 | abort() 69 | } 70 | 71 | // *ts = *arg would cause a write barrier, which is not allowed 72 | memmove(unsafe.Pointer(ts), unsafe.Pointer(arg), unsafe.Sizeof(threadstart{})) 73 | 74 | var attr pthread_attr 75 | var ign, oset sigset_t 76 | var p pthread_t 77 | var size size_t 78 | 79 | sigfillset(&ign) 80 | pthread_sigmask(SIG_SETMASK, &ign, &oset) 81 | 82 | pthread_attr_init(&attr) 83 | pthread_attr_getstacksize(&attr, &size) 84 | (*g)((*threadstart)(unsafe.Pointer(ts)).g).stack.hi = uintptr(size) 85 | 86 | var err int32 87 | 88 | for tries := 0; tries < 20; tries++ { 89 | err = pthread_create(&p, &attr, unsafe.Pointer(funcPC(threadentry_trampoline)), unsafe.Pointer(ts)) 90 | if err == 0 { 91 | pthread_detach(p) 92 | break 93 | } 94 | if err != int32(syscall.EAGAIN) { 95 | break 96 | } 97 | ts := timespec{tv_sec: 0, tv_nsec: (tries + 1) * 1000 * 1000} 98 | nanosleep(&ts, nil) 99 | } 100 | 101 | pthread_sigmask(SIG_SETMASK, &oset, nil) 102 | 103 | if err != 0 { 104 | dprintf(2, "pthread_create failed: %s\n\000", strerror(err)) 105 | abort() 106 | } 107 | } 108 | 109 | func setg_trampoline(uintptr, unsafe.Pointer) 110 | 111 | //go:nosplit 112 | func threadentry(v unsafe.Pointer) uintptr { 113 | ts := *(*threadstart)(v) 114 | 115 | free(v) 116 | 117 | setg_trampoline(setg_func, ts.g) 118 | 119 | // faking funcs in go is a bit a... involved - but the following works :) 120 | fn := uintptr(unsafe.Pointer(&ts.fn)) 121 | (*(*func())(unsafe.Pointer(&fn)))() 122 | 123 | return 0 124 | } 125 | 126 | // The following functions are required by the runtime - otherwise it complains via panic that they are missing 127 | 128 | // do nothing - we don't support being a library for now 129 | // _cgo_notify_runtime_init_done (runtime/cgo/gcc_libinit.c) 130 | //go:nosplit 131 | func x_cgo_notify_runtime_init_done() {} 132 | 133 | // _cgo_setenv(char **arg) (runtime/cgo/gcc_setenv.c) 134 | //go:nosplit 135 | func x_cgo_setenv(arg [2]*byte) { 136 | setenv(arg[0], arg[1], 1) 137 | } 138 | 139 | // _cgo_unsetenv(char *arg) (runtime/cgo/gcc_setenv.c) 140 | //go:nosplit 141 | func x_cgo_unsetenv(arg *byte) { 142 | unsetenv(arg) 143 | } 144 | -------------------------------------------------------------------------------- /internal/ffi/call_amd64.s: -------------------------------------------------------------------------------- 1 | #include "textflag.h" 2 | #include "go_asm.h" 3 | 4 | // runtime has #include "go_asm.h" 5 | // we need to fake the defines here: 6 | #define slice_array 0 7 | #define slice_len 8 8 | #define slice_cap 16 9 | 10 | 11 | #define LOADREG(off, target) \ 12 | MOVLQSX spec_intargs+argument__size*off(R12), AX \ 13 | TESTQ AX, AX \ 14 | JS xmm \ 15 | MOVWQZX AX, R11 \ 16 | SHRL $16, AX \ 17 | ADDQ R13, R11 \ 18 | CMPB AX, $const_type64 \ 19 | JNE 3(PC) \ 20 | MOVQ 0(R11), target \ // 64bit 21 | JMP 20(PC) \ 22 | CMPB AX, $const_typeS32 \ 23 | JNE 3(PC) \ 24 | MOVLQSX 0(R11), target \ // signed 32 bit 25 | JMP 18(PC) \ 26 | CMPB AX, $const_typeU32 \ 27 | JNE 3(PC) \ 28 | MOVLQZX 0(R11), target \ // unsigned 32 bit 29 | JMP 14(PC) \ 30 | CMPB AX, $const_typeS16 \ 31 | JNE 3(PC) \ 32 | MOVWQSX 0(R11), target \ // signed 16 bit 33 | JMP 10(PC) \ 34 | CMPB AX, $const_typeU16 \ 35 | JNE 3(PC) \ 36 | MOVWQZX 0(R11), target \ // unsigned 16 bit 37 | JMP 6(PC) \ 38 | CMPB AX, $const_typeS8 \ 39 | JNE 3(PC) \ 40 | MOVBQSX 0(R11), target \ // signed 8 bit 41 | JMP 2(PC) \ 42 | MOVBQZX 0(R11), target // unsigned 8 bit 43 | 44 | #define LOADXMMREG(off, target) \ 45 | MOVLQSX spec_xmmargs+argument__size*off(R12), AX \ 46 | TESTQ AX, AX \ 47 | JS prepared \ 48 | MOVWQZX AX, R11 \ 49 | SHRL $16, AX \ 50 | ADDQ R13, R11 \ 51 | CMPB AX, $const_typeDouble \ 52 | JNE 3(PC) \ 53 | MOVSD 0(R11), target \ // float 64bit 54 | JMP 2(PC) \ 55 | MOVSS 0(R11), target \ // float 32bit 56 | 57 | TEXT ·cgocall(SB),NOSPLIT,$0 58 | JMP runtime·cgocall(SB) 59 | 60 | // pass struct { &args, &spec } to cgocall 61 | TEXT ·callWrapper(SB),NOSPLIT|WRAPPER,$32 62 | MOVQ DX, 24(SP) 63 | LEAQ argframe+0(FP), AX 64 | MOVQ AX, 16(SP) 65 | LEAQ 16(SP), AX 66 | MOVQ AX, 8(SP) 67 | LEAQ asmcall(SB), AX 68 | MOVQ AX, 0(SP) 69 | CALL ·cgocall(SB) 70 | RET 71 | 72 | TEXT asmcall(SB),NOSPLIT,$0 73 | MOVQ 8(DI), R12 // spec (preserved) 74 | MOVQ 0(DI), R13 // base of args (preserved) 75 | MOVQ SP, R14 // stack for restoring later on (preserved) 76 | 77 | ANDQ $~0x1F, SP // 32 byte alignment for cdecl (in case someone wants to pass __m256 on the stack) 78 | // for no __m256 16 byte would be ok 79 | // this is actually already done by cgocall - but asmcall was called from there and destroys that :( 80 | 81 | MOVQ spec_stack+slice_len(R12), AX // length of stack registers 82 | TESTQ AX, AX 83 | JZ reg 84 | 85 | // ok we have stack arguments so let's do that first 86 | 87 | // Fix alignment depending on number of arguments 88 | MOVQ AX, BX 89 | ANDQ $3, BX 90 | SHLQ $3, BX 91 | SUBQ BX, SP 92 | 93 | MOVQ spec_stack+slice_array(R12), BX 94 | 95 | next: 96 | DECQ AX 97 | MOVQ (BX)(AX*argument__size), CX 98 | //check type and push to stack 99 | MOVWQZX CX, R11 100 | SHRL $16, CX 101 | ADDQ R13, R11 102 | 103 | #define LOADSTACK(type, instr) \ 104 | CMPB CX, type \ 105 | JNE 7(PC) \ 106 | SUBQ $8, SP \ 107 | instr 0(R11), CX \ 108 | instr CX, 0(SP) \ 109 | TESTQ AX, AX \ 110 | JZ reg \ 111 | JMP next 112 | 113 | LOADSTACK($const_type64, MOVQ) 114 | LOADSTACK($const_typeU32, MOVL) 115 | LOADSTACK($const_typeU16, MOVW) 116 | LOADSTACK($const_typeU8, MOVB) 117 | 118 | INT $3 119 | 120 | reg: 121 | // load register arguments 122 | LOADREG(0, DI) 123 | LOADREG(1, SI) 124 | LOADREG(2, DX) 125 | LOADREG(3, CX) 126 | LOADREG(4, R8) 127 | LOADREG(5, R9) 128 | 129 | xmm: 130 | // load xmm arguments 131 | LOADXMMREG(0, X0) 132 | LOADXMMREG(1, X1) 133 | LOADXMMREG(2, X2) 134 | LOADXMMREG(3, X3) 135 | LOADXMMREG(4, X4) 136 | LOADXMMREG(5, X5) 137 | LOADXMMREG(6, X6) 138 | LOADXMMREG(7, X7) 139 | 140 | prepared: 141 | // load number of vector registers 142 | MOVBQSX spec_rax(R12), AX 143 | 144 | // do the actuall call 145 | CALL spec_fn(R12) 146 | 147 | MOVQ R14, SP 148 | 149 | // TODO: check R13, if it still points to the correct stack! (could happen if we have a callback into go that splits the stack) 150 | 151 | // store ret 152 | MOVLQSX spec_ret(R12), BX 153 | TESTQ BX, BX 154 | JS DONE 155 | MOVWQZX BX, R11 156 | SHRL $16, BX 157 | ADDQ R13, R11 158 | 159 | CMPB BX, $const_type64 160 | JNE 3(PC) 161 | MOVQ AX, (R11) 162 | JMP DONE 163 | 164 | CMPB BX, $const_typeU32 165 | JNE 3(PC) 166 | MOVL AX, (R11) 167 | JMP DONE 168 | 169 | CMPB BX, $const_typeU16 170 | JNE 3(PC) 171 | MOVW AX, (R11) 172 | JMP DONE 173 | 174 | CMPB BX, $const_typeU8 175 | JNE 3(PC) 176 | MOVB AX, (R11) 177 | JMP DONE 178 | 179 | CMPB BX, $const_typeDouble 180 | JNE 3(PC) 181 | MOVSD X0, (R11) 182 | JMP DONE 183 | 184 | CMPB BX, $const_typeFloat 185 | JNE 3(PC) 186 | MOVSS X0, (R11) 187 | JMP DONE 188 | 189 | INT $3 190 | 191 | DONE: 192 | RET 193 | -------------------------------------------------------------------------------- /steps/2_go/test/_obj/_cgo_gotypes.go: -------------------------------------------------------------------------------- 1 | // Code generated by cmd/cgo; DO NOT EDIT. 2 | 3 | package main 4 | 5 | import "unsafe" 6 | 7 | import _ "runtime/cgo" 8 | 9 | import "syscall" 10 | 11 | var _ syscall.Errno 12 | func _Cgo_ptr(ptr unsafe.Pointer) unsafe.Pointer { return ptr } 13 | 14 | //go:linkname _Cgo_always_false runtime.cgoAlwaysFalse 15 | var _Cgo_always_false bool 16 | //go:linkname _Cgo_use runtime.cgoUse 17 | func _Cgo_use(interface{}) 18 | type _Ctype_FILE = _Ctype_struct__IO_FILE 19 | 20 | type _Ctype__IO_lock_t = _Ctype_void 21 | 22 | type _Ctype___off64_t = _Ctype_long 23 | 24 | type _Ctype___off_t = _Ctype_long 25 | 26 | type _Ctype_char int8 27 | 28 | type _Ctype_int int32 29 | 30 | type _Ctype_long int64 31 | 32 | type _Ctype_schar int8 33 | 34 | type _Ctype_size_t = _Ctype_ulong 35 | 36 | type _Ctype_struct__IO_FILE struct { 37 | _flags _Ctype_int 38 | _IO_read_ptr *_Ctype_char 39 | _IO_read_end *_Ctype_char 40 | _IO_read_base *_Ctype_char 41 | _IO_write_base *_Ctype_char 42 | _IO_write_ptr *_Ctype_char 43 | _IO_write_end *_Ctype_char 44 | _IO_buf_base *_Ctype_char 45 | _IO_buf_end *_Ctype_char 46 | _IO_save_base *_Ctype_char 47 | _IO_backup_base *_Ctype_char 48 | _IO_save_end *_Ctype_char 49 | _markers *_Ctype_struct__IO_marker 50 | _chain *_Ctype_struct__IO_FILE 51 | _fileno _Ctype_int 52 | _flags2 _Ctype_int 53 | _old_offset _Ctype___off_t 54 | _cur_column _Ctype_ushort 55 | _vtable_offset _Ctype_schar 56 | _shortbuf [1]_Ctype_char 57 | _lock unsafe.Pointer 58 | _offset _Ctype___off64_t 59 | _codecvt *_Ctype_struct__IO_codecvt 60 | _wide_data *_Ctype_struct__IO_wide_data 61 | _freeres_list *_Ctype_struct__IO_FILE 62 | _freeres_buf unsafe.Pointer 63 | __pad5 _Ctype_size_t 64 | _mode _Ctype_int 65 | _unused2 [20]_Ctype_char 66 | } 67 | 68 | type _Ctype_struct__IO_codecvt struct{} 69 | 70 | type _Ctype_struct__IO_marker struct{} 71 | 72 | type _Ctype_struct__IO_wide_data struct{} 73 | 74 | type _Ctype_ulong uint64 75 | 76 | type _Ctype_ushort uint16 77 | 78 | type _Ctype_void [0]byte 79 | 80 | //go:linkname _cgo_runtime_cgocall runtime.cgocall 81 | func _cgo_runtime_cgocall(unsafe.Pointer, uintptr) int32 82 | 83 | //go:linkname _cgo_runtime_cgocallback runtime.cgocallback 84 | func _cgo_runtime_cgocallback(unsafe.Pointer, unsafe.Pointer, uintptr, uintptr) 85 | 86 | //go:linkname _cgoCheckPointer runtime.cgoCheckPointer 87 | func _cgoCheckPointer(interface{}, ...interface{}) 88 | 89 | //go:linkname _cgoCheckResult runtime.cgoCheckResult 90 | func _cgoCheckResult(interface{}) 91 | 92 | //go:cgo_import_static _cgo_194c66e4342c_Cfunc_fputs 93 | //go:linkname __cgofn__cgo_194c66e4342c_Cfunc_fputs _cgo_194c66e4342c_Cfunc_fputs 94 | var __cgofn__cgo_194c66e4342c_Cfunc_fputs byte 95 | var _cgo_194c66e4342c_Cfunc_fputs = unsafe.Pointer(&__cgofn__cgo_194c66e4342c_Cfunc_fputs) 96 | 97 | //go:cgo_unsafe_args 98 | func _Cfunc_fputs(p0 *_Ctype_char, p1 *_Ctype_struct__IO_FILE) (r1 _Ctype_int) { 99 | _cgo_runtime_cgocall(_cgo_194c66e4342c_Cfunc_fputs, uintptr(unsafe.Pointer(&p0))) 100 | if _Cgo_always_false { 101 | _Cgo_use(p0) 102 | _Cgo_use(p1) 103 | } 104 | return 105 | } 106 | //go:cgo_import_static _cgo_194c66e4342c_Cfunc_putc 107 | //go:linkname __cgofn__cgo_194c66e4342c_Cfunc_putc _cgo_194c66e4342c_Cfunc_putc 108 | var __cgofn__cgo_194c66e4342c_Cfunc_putc byte 109 | var _cgo_194c66e4342c_Cfunc_putc = unsafe.Pointer(&__cgofn__cgo_194c66e4342c_Cfunc_putc) 110 | 111 | //go:cgo_unsafe_args 112 | func _Cfunc_putc(p0 _Ctype_int, p1 *_Ctype_struct__IO_FILE) (r1 _Ctype_int) { 113 | _cgo_runtime_cgocall(_cgo_194c66e4342c_Cfunc_putc, uintptr(unsafe.Pointer(&p0))) 114 | if _Cgo_always_false { 115 | _Cgo_use(p0) 116 | _Cgo_use(p1) 117 | } 118 | return 119 | } 120 | //go:cgo_import_static _cgo_194c66e4342c_Cfunc_puts 121 | //go:linkname __cgofn__cgo_194c66e4342c_Cfunc_puts _cgo_194c66e4342c_Cfunc_puts 122 | var __cgofn__cgo_194c66e4342c_Cfunc_puts byte 123 | var _cgo_194c66e4342c_Cfunc_puts = unsafe.Pointer(&__cgofn__cgo_194c66e4342c_Cfunc_puts) 124 | 125 | //go:cgo_unsafe_args 126 | func _Cfunc_puts(p0 *_Ctype_char) (r1 _Ctype_int) { 127 | _cgo_runtime_cgocall(_cgo_194c66e4342c_Cfunc_puts, uintptr(unsafe.Pointer(&p0))) 128 | if _Cgo_always_false { 129 | _Cgo_use(p0) 130 | } 131 | return 132 | } 133 | //go:cgo_import_static _cgo_194c66e4342c_Cmacro_stdout 134 | //go:linkname __cgofn__cgo_194c66e4342c_Cmacro_stdout _cgo_194c66e4342c_Cmacro_stdout 135 | var __cgofn__cgo_194c66e4342c_Cmacro_stdout byte 136 | var _cgo_194c66e4342c_Cmacro_stdout = unsafe.Pointer(&__cgofn__cgo_194c66e4342c_Cmacro_stdout) 137 | 138 | //go:cgo_unsafe_args 139 | func _Cmacro_stdout() (r1 *_Ctype_struct__IO_FILE) { 140 | _cgo_runtime_cgocall(_cgo_194c66e4342c_Cmacro_stdout, uintptr(unsafe.Pointer(&r1))) 141 | if _Cgo_always_false { 142 | } 143 | return 144 | } 145 | //go:cgo_import_static _cgo_194c66e4342c_Cfunc_strcat 146 | //go:linkname __cgofn__cgo_194c66e4342c_Cfunc_strcat _cgo_194c66e4342c_Cfunc_strcat 147 | var __cgofn__cgo_194c66e4342c_Cfunc_strcat byte 148 | var _cgo_194c66e4342c_Cfunc_strcat = unsafe.Pointer(&__cgofn__cgo_194c66e4342c_Cfunc_strcat) 149 | 150 | //go:cgo_unsafe_args 151 | func _Cfunc_strcat(p0 *_Ctype_char, p1 *_Ctype_char) (r1 *_Ctype_char) { 152 | _cgo_runtime_cgocall(_cgo_194c66e4342c_Cfunc_strcat, uintptr(unsafe.Pointer(&p0))) 153 | if _Cgo_always_false { 154 | _Cgo_use(p0) 155 | _Cgo_use(p1) 156 | } 157 | return 158 | } 159 | -------------------------------------------------------------------------------- /steps/3_goffi/ffi/ffi.go: -------------------------------------------------------------------------------- 1 | package ffi 2 | 3 | import ( 4 | "reflect" 5 | "strings" 6 | "unsafe" 7 | ) 8 | 9 | //go:generate go tool compile -asmhdr ffi.h ffi.go 10 | 11 | type argtype uint16 12 | 13 | const ( 14 | type64 argtype = 0 // movq 64 bit 15 | typeS32 argtype = 1 // movlqsx signed 32 bit 16 | typeU32 argtype = 2 // movlqzx unsigned 32 bit 17 | typeS16 argtype = 3 // movwqsx signed 16 bit 18 | typeU16 argtype = 4 // movwqzx unsigned 16 bit 19 | typeS8 argtype = 5 // movbqsx signed 8 bit 20 | typeU8 argtype = 6 // movbqzx unsigned 8 bit 21 | typeDouble argtype = 7 // movsd 64 bit 22 | typeFloat argtype = 8 // movss 32 bit 23 | typeUnused argtype = 0xFFFF 24 | ) 25 | 26 | type argument struct { 27 | offset uint16 28 | t argtype 29 | } 30 | 31 | // Spec is the callspec needed to do the actuall call 32 | type Spec struct { 33 | fn uintptr 34 | base uintptr 35 | stack []argument 36 | intargs [6]argument 37 | xmmargs [8]argument 38 | ret0 argument 39 | ret1 argument 40 | xmmret0 argument 41 | xmmret1 argument 42 | rax uint8 43 | } 44 | 45 | var sliceOffset = reflect.TypeOf(reflect.SliceHeader{}).Field(0).Offset 46 | 47 | func fieldToOffset(k reflect.StructField, t string) (argument, bool) { 48 | switch k.Type.Kind() { 49 | case reflect.Slice: 50 | return argument{uint16(k.Offset + sliceOffset), type64}, false 51 | case reflect.Int, reflect.Uint, reflect.Uint64, reflect.Int64, reflect.Ptr: 52 | return argument{uint16(k.Offset), type64}, false 53 | case reflect.Int32: 54 | return argument{uint16(k.Offset), typeS32}, false 55 | case reflect.Uint32: 56 | return argument{uint16(k.Offset), typeU32}, false 57 | case reflect.Int16: 58 | return argument{uint16(k.Offset), typeS16}, false 59 | case reflect.Uint16: 60 | return argument{uint16(k.Offset), typeU16}, false 61 | case reflect.Int8: 62 | return argument{uint16(k.Offset), typeS8}, false 63 | case reflect.Uint8, reflect.Bool: 64 | return argument{uint16(k.Offset), typeU8}, false 65 | case reflect.Float32: 66 | return argument{uint16(k.Offset), typeFloat}, true 67 | case reflect.Float64: 68 | return argument{uint16(k.Offset), typeDouble}, true 69 | } 70 | panic("Unknown Type") 71 | } 72 | 73 | // FIXME: we don't support stuff > 64 bit 74 | 75 | // MakeSpec builds a call specification for the given arguments 76 | func MakeSpec(fn uintptr, args interface{}) Spec { 77 | v := reflect.ValueOf(args) 78 | for v.Kind() == reflect.Ptr { 79 | v = v.Elem() 80 | } 81 | t := v.Type() 82 | 83 | var spec Spec 84 | 85 | spec.fn = fn 86 | 87 | spec.ret0.t = typeUnused 88 | spec.ret1.t = typeUnused 89 | spec.xmmret0.t = typeUnused 90 | spec.xmmret1.t = typeUnused 91 | 92 | haveRet := false 93 | 94 | intreg := 0 95 | xmmreg := 0 96 | 97 | ARGS: 98 | for i := 0; i < t.NumField(); i++ { 99 | f := t.Field(i) 100 | tags := strings.Split(f.Tag.Get("ffi"), ",") 101 | ret := false 102 | st := "" 103 | for _, tag := range tags { 104 | if tag == "ignore" { 105 | continue ARGS 106 | } 107 | if tag == "ret" { 108 | if haveRet == true { 109 | panic("Only one return argument allowed") 110 | } 111 | ret = true 112 | haveRet = true 113 | continue 114 | } 115 | if strings.HasPrefix(tag, "type=") { 116 | st = tag[5:] 117 | } 118 | } 119 | if ret { 120 | off, xmm := fieldToOffset(f, st) 121 | if xmm { 122 | spec.xmmret0 = off 123 | } else { 124 | spec.ret0 = off 125 | } 126 | // FIXME ret1/xmmret1! - only needed for types > 64 bit 127 | continue 128 | } 129 | off, xmm := fieldToOffset(f, st) 130 | if xmm { 131 | if xmmreg < 8 { 132 | spec.xmmargs[xmmreg] = off 133 | xmmreg++ 134 | } else { 135 | spec.stack = append(spec.stack, off) 136 | } 137 | } else { 138 | if intreg < 6 { 139 | spec.intargs[intreg] = off 140 | intreg++ 141 | } else { 142 | spec.stack = append(spec.stack, off) 143 | } 144 | } 145 | } 146 | for i := intreg; i < 6; i++ { 147 | spec.intargs[i].t = typeUnused 148 | } 149 | for i := xmmreg; i < 8; i++ { 150 | spec.xmmargs[i].t = typeUnused 151 | } 152 | spec.rax = uint8(xmmreg) 153 | return spec 154 | } 155 | 156 | // Call calls the given spec with the given arguments 157 | func (spec Spec) Call(args unsafe.Pointer) { 158 | spec.base = uintptr(args) 159 | 160 | entersyscall() 161 | asmcgocall(unsafe.Pointer(asmcallptr), uintptr(unsafe.Pointer(&spec))) 162 | exitsyscall() 163 | 164 | if _Cgo_always_false { 165 | _Cgo_use(args) 166 | _Cgo_use(spec) 167 | } 168 | } 169 | 170 | //go:linkname _Cgo_always_false runtime.cgoAlwaysFalse 171 | var _Cgo_always_false bool 172 | 173 | //go:linkname _Cgo_use runtime.cgoUse 174 | func _Cgo_use(interface{}) 175 | 176 | //go:linkname asmcgocall runtime.asmcgocall 177 | func asmcgocall(unsafe.Pointer, uintptr) int32 178 | 179 | //go:linkname entersyscall runtime.entersyscall 180 | func entersyscall() 181 | 182 | //go:linkname exitsyscall runtime.exitsyscall 183 | func exitsyscall() 184 | 185 | func asmcall() 186 | 187 | //go:linkname x_cgo_init x_cgo_init 188 | func x_cgo_init() 189 | 190 | // force _cgo_init into the .data segment (instead of .bss), so our "linker" can overwrite its contents 191 | //go:linkname _cgo_init _cgo_init 192 | var _cgo_init = uintptr(10) 193 | 194 | type emptyComplex64 struct { 195 | a complex64 196 | } 197 | type emptyComplex128 complex128 198 | 199 | func init() { 200 | if _Cgo_always_false { 201 | x_cgo_init() // prevent x_cgo_init from being optimized out 202 | } 203 | } 204 | 205 | //go:linkname funcPC runtime.funcPC 206 | func funcPC(f interface{}) uintptr 207 | 208 | var asmcallptr = funcPC(asmcall) 209 | -------------------------------------------------------------------------------- /steps/3_goffi/ffi/call_linux_amd64.s: -------------------------------------------------------------------------------- 1 | #include "textflag.h" 2 | #include "ffi.h" 3 | 4 | // runtime has #include "go_asm.h" 5 | // we need to fake the defines here: 6 | #define g_stack 0 7 | #define stack_lo 0 8 | #define slice_array 0 9 | #define slice_len 8 10 | #define slice_cap 16 11 | 12 | 13 | /* 14 | Frame layout: 15 | int: 16 | type64: movq 64 bit 17 | typeS32: movlqsx signed 32 bit 18 | typeU32: movlqzx unsigned 32 bit 19 | typeS16: movwqsx signed 16 bit 20 | typeU16: movwqzx unsigned 16 bit 21 | typeS8: movbqsx signed 8 bit 22 | typeU8: movbqzx unsigned 8 bit 23 | 24 | float: 25 | typeDouble: movsd 64 bit 26 | typeFloat: movss 32 bit 27 | */ 28 | 29 | #define LOADREG(off, target) \ 30 | MOVLQSX Spec_intargs+argument__size*off(R12), AX \ 31 | TESTQ AX, AX \ 32 | JS xmm \ 33 | MOVWQZX AX, R11 \ 34 | SHRL $16, AX \ 35 | ADDQ R13, R11 \ 36 | CMPB AX, $const_type64 \ 37 | JNE 3(PC) \ 38 | MOVQ 0(R11), target \ // 64bit 39 | JMP 20(PC) \ 40 | CMPB AX, $const_typeS32 \ 41 | JNE 3(PC) \ 42 | MOVLQSX 0(R11), target \ // signed 32 bit 43 | JMP 18(PC) \ 44 | CMPB AX, $const_typeU32 \ 45 | JNE 3(PC) \ 46 | MOVLQZX 0(R11), target \ // unsigned 32 bit 47 | JMP 14(PC) \ 48 | CMPB AX, $const_typeS16 \ 49 | JNE 3(PC) \ 50 | MOVWQSX 0(R11), target \ // signed 16 bit 51 | JMP 10(PC) \ 52 | CMPB AX, $const_typeU16 \ 53 | JNE 3(PC) \ 54 | MOVWQZX 0(R11), target \ // unsigned 16 bit 55 | JMP 6(PC) \ 56 | CMPB AX, $const_typeS8 \ 57 | JNE 3(PC) \ 58 | MOVBQSX 0(R11), target \ // signed 8 bit 59 | JMP 2(PC) \ 60 | MOVBQZX 0(R11), target // unsigned 8 bit 61 | 62 | #define LOADXMMREG(off, target) \ 63 | MOVLQSX Spec_xmmargs+argument__size*off(R12), AX \ 64 | TESTQ AX, AX \ 65 | JS prepared \ 66 | MOVWQZX AX, R11 \ 67 | SHRL $16, AX \ 68 | ADDQ R13, R11 \ 69 | CMPB AX, $const_typeDouble \ 70 | JNE 3(PC) \ 71 | MOVSD 0(R11), target \ // float 64bit 72 | JMP 2(PC) \ 73 | MOVSS 0(R11), target \ // float 32bit 74 | 75 | 76 | // func asmcall() 77 | TEXT ·asmcall(SB),NOSPLIT,$0 78 | MOVQ DI, R12 // FRAME (preserved) 79 | MOVQ Spec_base(R12), R13 // base 80 | MOVQ SP, R14 // stack 81 | 82 | ANDQ $~0x1F, SP // 32 byte alignment for cdecl (in case someone wants to pass __m256 on the stack) 83 | // for no __m256 16 byte would be ok 84 | // this is actually already done by cgocall - but asmcall was called from there and destroys that :( 85 | 86 | MOVQ Spec_stack+slice_len(R12), AX // length of stack registers 87 | TESTQ AX, AX 88 | JZ reg 89 | 90 | // Fix alignment depending on number of arguments 91 | MOVQ AX, BX 92 | ANDQ $3, BX 93 | SHLQ $3, BX 94 | SUBQ BX, SP 95 | 96 | MOVQ Spec_stack+slice_array(R12), BX 97 | 98 | next: 99 | DECQ AX 100 | MOVQ 0(BX)(AX*argument__size), CX 101 | //check type and push to stack 102 | MOVWQZX CX, R11 103 | SHRL $16, CX 104 | ADDQ R13, R11 105 | 106 | CMPB CX, $const_type64 107 | JNE 7(PC) 108 | SUBQ $8, SP 109 | MOVQ 0(R11), CX 110 | MOVQ CX, 0(SP) 111 | TESTQ AX, AX 112 | JZ reg 113 | JMP next 114 | 115 | CMPB CX, $const_typeS32 116 | JNE 7(PC) 117 | SUBQ $8, SP 118 | MOVLQSX 0(R11), CX 119 | MOVQ CX, 0(SP) 120 | TESTQ AX, AX 121 | JZ reg 122 | JMP next 123 | 124 | CMPB CX, $const_typeU32 125 | JNE 7(PC) 126 | SUBQ $8, SP 127 | MOVLQZX 0(R11), CX 128 | MOVQ CX, 0(SP) 129 | TESTQ AX, AX 130 | JZ reg 131 | JMP next 132 | 133 | CMPB CX, $const_typeS16 134 | JNE 7(PC) 135 | SUBQ $8, SP 136 | MOVWQSX 0(R11), CX 137 | MOVQ CX, 0(SP) 138 | TESTQ AX, AX 139 | JZ reg 140 | JMP next 141 | 142 | CMPB CX, $const_typeU16 143 | JNE 7(PC) 144 | SUBQ $8, SP 145 | MOVWQZX 0(R11), CX 146 | MOVQ CX, 0(SP) 147 | TESTQ AX, AX 148 | JZ reg 149 | JMP next 150 | 151 | CMPB CX, $const_typeS8 152 | JNE 7(PC) 153 | SUBQ $8, SP 154 | MOVBQSX 0(R11), CX 155 | MOVQ CX, 0(SP) 156 | TESTQ AX, AX 157 | JZ reg 158 | JMP next 159 | 160 | CMPB CX, $const_typeU8 161 | JNE 7(PC) 162 | SUBQ $8, SP 163 | MOVBQZX 0(R11), CX 164 | MOVQ CX, 0(SP) 165 | TESTQ AX, AX 166 | JZ reg 167 | JMP next 168 | 169 | CMPB CX, $const_typeU8 170 | JNE 7(PC) 171 | SUBQ $8, SP 172 | MOVBQZX 0(R11), CX 173 | MOVQ CX, 0(SP) 174 | TESTQ AX, AX 175 | JZ reg 176 | JMP next 177 | 178 | CMPB CX, $const_typeDouble 179 | JNE 7(PC) 180 | SUBQ $8, SP 181 | MOVSD 0(R11), X0 182 | MOVSD X0, 0(SP) 183 | TESTQ AX, AX 184 | JZ reg 185 | JMP next 186 | 187 | SUBQ $8, SP 188 | MOVSS 0(R11), X0 189 | MOVSS X0, 0(SP) 190 | TESTQ AX, AX 191 | JZ reg 192 | JMP next 193 | 194 | reg: 195 | // load register arguments 196 | LOADREG(0, DI) 197 | LOADREG(1, SI) 198 | LOADREG(2, DX) 199 | LOADREG(3, CX) 200 | LOADREG(4, R8) 201 | LOADREG(5, R9) 202 | 203 | xmm: 204 | // load xmm arguments 205 | LOADXMMREG(0, X0) 206 | LOADXMMREG(1, X1) 207 | LOADXMMREG(2, X2) 208 | LOADXMMREG(3, X3) 209 | LOADXMMREG(4, X4) 210 | LOADXMMREG(5, X5) 211 | LOADXMMREG(6, X6) 212 | LOADXMMREG(7, X7) 213 | 214 | prepared: 215 | // load number of vector registers 216 | MOVBQZX Spec_rax(R12), AX 217 | 218 | // do the actuall call 219 | CALL (R12) 220 | 221 | MOVQ R14, SP 222 | 223 | // store ret0 224 | MOVLQSX Spec_ret0(R12), BX 225 | TESTQ BX, BX 226 | JS xmmret0 227 | MOVWQZX BX, R11 228 | SHRL $16, BX 229 | ADDQ R13, R11 230 | CMPB BX, $0 231 | JNE 3(PC) 232 | MOVQ AX, (R11) 233 | JMP ret1 234 | CMPB BX, $2 235 | JGT 3(PC) 236 | MOVL AX, (R11) 237 | JMP ret1 238 | CMPB BX, $4 239 | JGT 3(PC) 240 | MOVW AX, (R11) 241 | JMP ret1 242 | MOVB AX, (R11) 243 | 244 | ret1: 245 | // store ret1 246 | MOVLQSX Spec_ret1(R12), BX 247 | TESTQ BX, BX 248 | JS DONE 249 | MOVWQZX BX, R11 250 | SHRL $16, BX 251 | ADDQ R13, R11 252 | CMPB BX, $0 253 | JNE 3(PC) 254 | MOVQ DX, (R11) 255 | JMP ret1 256 | CMPB BX, $2 257 | JGT 3(PC) 258 | MOVL DX, (R11) 259 | JMP ret1 260 | CMPB BX, $4 261 | JGT 3(PC) 262 | MOVW DX, (R11) 263 | JMP ret1 264 | MOVB DX, (R11) 265 | 266 | xmmret0: 267 | // store xmmret0 268 | MOVLQSX Spec_xmmret0(R12), BX 269 | TESTQ BX, BX 270 | JS DONE 271 | MOVWQZX BX, R11 272 | SHRL $16, BX 273 | ADDQ R13, R11 274 | CMPB BX, $7 275 | JNE 3(PC) 276 | MOVSD X0, (R11) 277 | JMP xmmret1 278 | MOVSS X0, (R11) 279 | 280 | xmmret1: 281 | // store xmmret1 282 | MOVLQSX Spec_xmmret1(R12), BX 283 | TESTQ BX, BX 284 | JS DONE 285 | MOVWQZX BX, R11 286 | SHRL $16, BX 287 | ADDQ R13, R11 288 | CMPB BX, $7 289 | JNE 3(PC) 290 | MOVSD X1, (R11) 291 | JMP xmmret1 292 | MOVSS X1, (R11) 293 | 294 | DONE: 295 | RET 296 | 297 | 298 | GLOBL pthread_attr_init__dynload(SB), NOPTR, $8 299 | GLOBL pthread_attr_getstacksize__dynload(SB), NOPTR, $8 300 | GLOBL pthread_attr_destroy__dynload(SB), NOPTR, $8 301 | 302 | TEXT x_cgo_init(SB),NOSPLIT,$512 // size_t size (8 byte) + unknown pthread_attr_t - hopefully this is big enough 303 | MOVQ DI, R12 // g 304 | 305 | // pthread_attr_init(8(SP)) 306 | LEAQ 8(SP), DI 307 | MOVQ $pthread_attr_init__dynload(SB), R11 308 | CALL (R11) 309 | 310 | // pthread_attr_init(8(SP), 0(SP)) 311 | LEAQ 8(SP), DI 312 | LEAQ 0(SP), SI 313 | MOVQ $pthread_attr_getstacksize__dynload(SB), R11 314 | CALL (R11) 315 | 316 | // g->stacklo = &size - size + 4096 317 | LEAQ 0x1000(SP), AX 318 | SUBQ 0(SP), AX 319 | MOVQ AX, (g_stack+stack_lo)(R12) 320 | 321 | // pthread_attr_init(8(SP)) 322 | LEAQ 8(SP), DI 323 | MOVQ $pthread_attr_destroy__dynload(SB), R11 324 | CALL (R11) 325 | 326 | RET 327 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | nocgo 2 | ===== 3 | 4 | Tested on go1.11 and go1.12. 5 | 6 | [![GoDoc](https://godoc.org/github.com/notti/nocgo?status.svg)](https://godoc.org/github.com/notti/nocgo) 7 | 8 | This repository/package contains a *proof of concept* for calling into C code *without* using cgo. 9 | 10 | > **WARNING!** This is meant as a proof of concept and subject to changes. 11 | Furthermore this is highly experimental code. DO NOT USE IN PRODUCTION. 12 | This could cause lots of issues from random crashes (there are tests - but there is definitely stuff that's not tested) to teaching your gopher to talk [C gibberish](https://cdecl.org/). 13 | 14 | > **WARNING** nocgo supports both cgo and missing cgo as environment. So if you want to ensure cgo not being used don't forget `CGO_ENABLED=0` as environment variable to `go build`. 15 | 16 | Todo 17 | ---- 18 | 19 | - Callbacks into go 20 | - Structures 21 | 22 | When that's done write up a proposal for golang inclusion. 23 | 24 | Usage 25 | ----- 26 | 27 | Libraries can be loaded and unloaded similar to `dlopen` and `dlclose`, but acquiring symbols (i.e., functions, global variables) is a bit different, since a function specification (i.e., arguments, types, return type) is also needed. Furthermore, C-types must be translated to go-types and vice versa. 28 | 29 | This works by providing a function specification as a pointer to a function variable. A call to `lib.Func` will examine arguments and eventual return value (only one or no return values allowed!), and set the function variable to a wrapper that will call into the desired C-function. 30 | 31 | ### Type Mappings 32 | 33 | Go types will be mapped to C-types according to the following table: 34 | 35 | Go type | C Type 36 | --------------------------------------------- | ------ 37 | `int8`, `byte` | `char` 38 | `uint8`, `bool` | `unsigned char` 39 | `int16` | `short` 40 | `uint16` | `unsigned short` 41 | `int32` | `int` 42 | `uint32` | `unsigned int` 43 | `int64` | `long` 44 | `uint64` | `unsigned long` 45 | `float32` | `float` 46 | `float64` | `double` 47 | `[]`, `uintptr`, `reflect.UnsafePointer`, `*` | `*` 48 | 49 | The last line means that slices and pointers are mapped to pointers in C. Pointers to structs are possible. 50 | 51 | Passing `struct`, `complex`, and callback functions is not (yet) supported. 52 | 53 | > **WARNING** `struct`s that are referenced **must** follow C alignment rules! There is **no** type checking, since this is actually not possible due to libraries not knowing their types... 54 | 55 | Go `int` was deliberately left out to avoid confusion, since it has different sizes on different architectures. 56 | 57 | ### Example 58 | 59 | An example using `pcap_open_live` from libpcap (C-definition: `pcap_t *pcap_open_live(const char *device, int snaplen, int promisc, int to_ms, char *errbuf) 60 | `) could look like the following example: 61 | 62 | ```golang 63 | 64 | // Load the library 65 | lib, err := nocgo.Open("libpcap.so") 66 | if err != nil { 67 | log.Fatalln("Couldn't load libpcap: ", err) 68 | } 69 | 70 | // func specification 71 | var pcapOpenLive func(device []byte, snaplen int32, promisc int32, toMS int32, errbuf []byte) uintptr 72 | // Get a handle for the function 73 | if err := lib.Func("pcap_open_live", &pcapOpenLive); err != nil { 74 | log.Fatalln("Couldn't get pcap_open_live: ", err) 75 | } 76 | 77 | // Do the function call 78 | errbuf := make([]byte, 512) 79 | pcapHandle := pcapOpenLive(nocgo.MakeCString("lo"), 1500, 1, 100, errbuf) 80 | 81 | // Check return value 82 | if pcapHandle == 0 { 83 | log.Fatalf("Couldn't open %s: %s\n", "lo", nocgo.MakeGoStringFromSlice(errbuf)) 84 | } 85 | 86 | // pcapHandle can now be used as argument to the other libpcap functions 87 | ``` 88 | 89 | A full example is contained in [examplelibpcap](examplelibpcap) and another one in [example](example). 90 | 91 | > **WARNING** nocgo supports both cgo and missing cgo as environment. So if you want to ensure cgo not being used don't forget `CGO_ENABLED=0` as environment variable to `go build`. 92 | 93 | Supported Systems 94 | ----------------- 95 | 96 | * linux with glibc 97 | * FreeBSD
98 | *Errata:* FreeBSD requires the exported symbols `_environ` and `_progname`. This is only possible inside cgo or stdlib. So for building on FreeBSD, `-gcflags=github.com/notti/nocgo/fakecgo=-std` is required (This doesn't seem to work for `go test` - so examples work, but test does not)). 99 | 100 | With some small modifications probably all systems providing `dlopen` can be supported. Have a look at [dlopen_OS.go](dlopen_linux.go) and [symbols_OS.go](fakecgo/symbols_linux.go) in fakecgo. 101 | 102 | Supported Architectures 103 | ----------------------- 104 | 105 | * 386 106 | * amd64 107 | 108 | Implementing further architectures requires 109 | * Building trampolines for [fakecgo](fakecgo) (see below) 110 | * Implementing the cdecl callspec in [call_.go](call_amd64.go)/[.s](call_amd64.s) 111 | 112 | How does this work 113 | ------------------ 114 | 115 | ### nocgo 116 | 117 | nocgo imports `dlopen`, `dlclose`, `dlerror`, `dlsym` via `go:cgo_import_dynamic` in [dlopen_OS.go](dlopen_linux.go). `lib.Func` builds a specification on where to put which argument in [call_arch.go](call_amd64.go). go calls such a function by dereferencing, where it points to, provide this address in a register and call the first address that is stored there. nocgo uses this mechanism by putting a struct there, that contains the address to a wrapper followed by a pointer to the what `dlsym` provided and a calling specification. The provided wrapper uses `cgocall` from the runtime to call an assembly function and pass the spec and a pointer to the arguments to it. This assembly function is implemented in call_arch.s and it uses the specification to place the arguments into the right places, calls the pointer provided by `dlsym` and then puts the return argument into the right place if needed. 118 | 119 | This is basically what `libffi` does. So far cdecl for 386 (pass arguments on the stack in right to left order, return values are in AX/CX or ST0) and amd64 (pass arguments in registers DI, SI, DX, CX, R8, R9/X0-X7 and the stack in right to left order, number of floats in AX, fixup alignment of stack) are implemented. 120 | 121 | So far so simple. `cgocall` could actually be used to call a C function directly - but it is only capable of providing one argument! 122 | 123 | But there is a second issue. For simple C functions we could leave it at that (well we would need to use `asmcgocall`, because `cgocall` checks, if cgo is actually there...). But there is this thing called Thread Local Storage (TLS) that is not too happy about golang not setting that up correctly. This is already needed if you do `printf("%f", 1)` with glibc! 124 | 125 | So we need to provide some functionality that cgo normally provides, which is implemented in fakecgo: 126 | 127 | ### fakecgo 128 | 129 | go sets up it's own TLS during startup in runtime/asm_arch.s in `runtime·rt0_go`. We can easily prevent that by providing setting the global variable `_cgo_init` to something non-zero (easily achieved with `go:linkname` and setting a value). But this would crash go, since if this is the case, go actually calls the address inside this variable (well ok we can provide an empty function). 130 | 131 | Additionally, this would provide correct TLS only on the main thread. This works until one does a lot more than just call one function, so we need to fixup also some other stuff. 132 | 133 | So next step: set `runtime.is_cgo` to true (again - linkname to the rescue). But this will panic since now the runtime expects the global variables `_cgo_thread_start`, `_cgo_notify_runtime_init_done`, `_cgo_setenv`, and `_cgo_unsetenv` to point to something. Ok so let's just implement those. 134 | 135 | * `_cgo_notify_runtime_init_done` is easy - we don't need this one: empty function. 136 | * `_cgo_setenv` is also simple: just one function call to `setenv` 137 | * `_cgo_unsetenv` is the same. 138 | * `_cgo_init` queries the needed stack size to update g->stack so that runtime stack checks do the right thing (it also provides a setg function we come to that later...) 139 | * `_cgo_thread_start` is a bit more involved... It starts up a new thread with `pthread_create` and does a bit of setup. 140 | 141 | So this should be doable - right? 142 | 143 | Well easier said than done - those are implemented in C-code in runtime/cgo/*c presenting some kind of chicken and egg problem to us. 144 | 145 | So I started out with reimplementing those in go assembly (remember: we want to be cgo free) which is available in the tag asm. Since this is really cumbersome and needs a lot of code duplication, I experimented a bit if we can do better. 146 | 147 | Aaaand we can: 148 | 149 | [fakecgo/trampoline_arch.s](fakecgo/trampoline_amd64.s) contains the above mentioned entry points, and "converts" the C-calling conventions to go calling conventions (e.g. move register passed arguments to the stack). Then it calls the go functions in [fakecgo/cgo.go](fakecgo/cgo.go). 150 | 151 | Ok - but we still need all those pthread and C-library-functions. Well we can import the symbols (like with `dlopen`). So all we need is a way to call those: 152 | 153 | The trampoline file also contains an `asmlibccall6` function that can call C-functions with a maximum of 6 integer arguments and one return value. [fakecgo/libccall.go](fakecgo/libccall.go) maps this onto more convenient go functions with 1-6 arguments and [fakecgo/libcdefs.go](fakecgo/libcdefs.go) further maps those into nice functions that look like the C functions (e.g. `func pthread_create(thread *pthread_t, attr *pthread_attr, start, arg unsafe.Pointer) int32`). Well this was not exactly my idea - the runtime already does that for solaris and darwin (runtime/os_solaris.go, runtime/syscall_solaris.go, runtime/sys_solaris_amd64.s) - although my implementation here is kept a bit simpler since it only ever will be called from gocode pretending to be C. 154 | 155 | So now we can implement all the above mentioned cgo functions in pure (but sometimes a bit ugly) go in [fakecgo/cgo.go](fakecgo/cgo.go). Ugly, because those functions are called with lots of functionality missing! Writebarriers are **not** allowed, as are stack splits. 156 | 157 | The upside is, that the only arch dependent stuff are the trampolines (in assembly) and the only OS dependent stuff are the symbol imports. 158 | 159 | Except for freebsd (which needs two exported symbols, as mentioned above) all those things work outside the runtime and no special treatment is needed. Just import fakecgo and all the cgo setup just works (except if you use cgo at the same time - then the linker will complain). 160 | 161 | Benchmarks 162 | ---------- 163 | 164 | This will be a bit slower than cgo. Most of this is caused by argument rearranging: 165 | 166 | ### 386 167 | 168 | ``` 169 | name old time/op new time/op delta 170 | Empty-4 84.5ns ± 0% 86.4ns ± 2% +2.22% (p=0.000 n=8+8) 171 | Float2-4 87.9ns ± 1% 222.5ns ± 6% +153.20% (p=0.000 n=8+10) 172 | StackSpill3-4 116ns ± 1% 130ns ± 1% +12.04% (p=0.000 n=8+8) 173 | ``` 174 | 175 | Float is so slow since that type is at the end of the comparison chain. 176 | 177 | ### amd64 178 | 179 | ``` 180 | name old time/op new time/op delta 181 | Empty-4 76.8ns ±10% 80.1ns ± 9% +4.24% (p=0.041 n=10+10) 182 | Float2-4 78.4ns ± 5% 81.4ns ± 9% +3.80% (p=0.033 n=9+10) 183 | StackSpill3-4 96.2ns ± 5% 120.7ns ± 7% +25.46% (p=0.000 n=10+9) 184 | ``` 185 | -------------------------------------------------------------------------------- /test/build.go: -------------------------------------------------------------------------------- 1 | // Test generator and tests 2 | package main 3 | 4 | import ( 5 | "fmt" 6 | "log" 7 | "os" 8 | "strings" 9 | "text/template" 10 | ) 11 | 12 | type datatype int 13 | 14 | const ( 15 | void datatype = iota 16 | i8 17 | u8 18 | i16 19 | u16 20 | i32 21 | u32 22 | i64 23 | u64 24 | f32 25 | f64 26 | byteSlice 27 | ) 28 | 29 | type datatypeMap struct { 30 | golang, C, Cgo, Cgop string 31 | } 32 | 33 | var mappings = map[datatype]datatypeMap{ 34 | void: {"", "void", "", ""}, 35 | i8: {"int8", "char", "C.char", ""}, 36 | u8: {"uint8", "unsigned char", "C.uchar", ""}, 37 | i16: {"int16", "short", "C.short", ""}, 38 | u16: {"uint16", "unsigned short", "C.ushort", ""}, 39 | i32: {"int32", "int", "C.int", ""}, 40 | u32: {"uint32", "unsigned int", "C.uint", ""}, 41 | i64: {"int64", "long long", "C.longlong", ""}, 42 | u64: {"uint64", "unsigned long long", "C.ulonglong", ""}, 43 | f32: {"float32", "float", "C.float", ""}, 44 | f64: {"float64", "double", "C.double", ""}, 45 | byteSlice: {"[]byte", "char *", "unsafe.Pointer", "*C.char"}, 46 | } 47 | 48 | type Value struct { 49 | Type datatype 50 | CData interface{} 51 | GoData interface{} 52 | } 53 | 54 | func (v Value) Go() string { 55 | return mappings[v.Type].golang 56 | } 57 | 58 | func (v Value) C() string { 59 | return mappings[v.Type].C 60 | } 61 | 62 | func (v Value) Void() bool { 63 | return v.Type == void 64 | } 65 | 66 | type Argument struct { 67 | Name string 68 | Value 69 | } 70 | 71 | func (a Argument) Go() string { 72 | return a.Name + " " + mappings[a.Type].golang 73 | } 74 | 75 | func (a Argument) C() string { 76 | return mappings[a.Type].C + " " + a.Name 77 | } 78 | 79 | type Arguments []Argument 80 | 81 | func (a Arguments) Go() string { 82 | args := make([]string, len(a)) 83 | for i, arg := range a { 84 | args[i] = arg.Go() 85 | } 86 | return strings.Join(args, ", ") 87 | } 88 | 89 | func (a Arguments) C() string { 90 | args := make([]string, len(a)) 91 | for i, arg := range a { 92 | args[i] = arg.C() 93 | } 94 | return strings.Join(args, ", ") 95 | } 96 | 97 | func (a Arguments) Call() string { 98 | args := make([]string, len(a)) 99 | for i, arg := range a { 100 | if mappings[arg.Type].Cgo == "unsafe.Pointer" { 101 | args[i] = fmt.Sprintf("(%s)(%s(&%s[0]))", mappings[arg.Type].Cgop, mappings[arg.Type].Cgo, arg.Name) 102 | } else { 103 | args[i] = fmt.Sprintf("%s(%s)", mappings[arg.Type].Cgo, arg.Name) 104 | } 105 | } 106 | return strings.Join(args, ", ") 107 | } 108 | 109 | func (a Arguments) Value() string { 110 | args := make([]string, len(a)) 111 | for i, arg := range a { 112 | if arg.GoData != nil { 113 | args[i] = fmt.Sprint(arg.GoData) 114 | } else { 115 | args[i] = fmt.Sprint(arg.CData) 116 | } 117 | } 118 | return strings.Join(args, ", ") 119 | } 120 | 121 | type Test struct { 122 | Name string 123 | Ret Value 124 | Arguments Arguments 125 | CgoPre, CgoPost string 126 | NOCGOPre, NOCGOPost string 127 | Bench bool 128 | Multitest bool 129 | } 130 | 131 | func (t Test) NOCGO() string { 132 | args := make([]string, len(t.Arguments)) 133 | for i, arg := range t.Arguments { 134 | args[i] = arg.Go() 135 | } 136 | ret := "(" + strings.Join(args, ", ") + ")" 137 | if !t.Ret.Void() { 138 | ret += " " + t.Ret.Go() 139 | } 140 | return ret 141 | } 142 | 143 | func (t Test) DataInit() string { 144 | args := make([]string, len(t.Arguments)) 145 | for i, arg := range t.Arguments { 146 | if arg.GoData == nil { 147 | args[i] = fmt.Sprint(arg.CData) 148 | } else { 149 | args[i] = fmt.Sprint(arg.GoData) 150 | } 151 | } 152 | return strings.Join(args, ", ") 153 | } 154 | 155 | func (t Test) TestName() string { 156 | return "Test" + strings.Title(t.Name) 157 | } 158 | 159 | func (t Test) BenchmarkName() string { 160 | return "Benchmark" + strings.Title(t.Name) 161 | } 162 | 163 | var ccode = template.Must(template.New("ccode").Parse(`#include 164 | 165 | {{range .}}{{.Ret.C}} {{.Name}}({{.Arguments.C}}) { 166 | return{{with .Ret.CData}} {{.}}{{end}}; 167 | } 168 | 169 | {{end}} 170 | `)) 171 | 172 | var bridge = template.Must(template.New("bridge").Parse(`package testlib 173 | 174 | {{range .}}// {{.Ret.C}} {{.Name}}({{.Arguments.C}}); 175 | {{end}}import "C" 176 | 177 | import "unsafe" 178 | 179 | var _ = unsafe.Sizeof(0) 180 | 181 | {{range .}}func {{.Name}}({{.Arguments.Go}}) {{.Ret.Go}} { 182 | {{if not .Ret.Void}}return {{.Ret.Go}}({{end}}C.{{.Name}}({{.Arguments.Call}}){{if not .Ret.Void}}){{end}} 183 | } 184 | 185 | {{end}} 186 | 187 | `)) 188 | 189 | var testingCgo = template.Must(template.New("testingCgo").Parse(`package testlib 190 | 191 | import ( 192 | "testing" 193 | ) 194 | 195 | {{range .}}func {{.TestName}}(t *testing.T) { 196 | {{.CgoPre}} 197 | {{if .Ret.Void}}{{.Name}}({{.Arguments.Value}}){{else}}ret := {{.Name}}({{.Arguments.Value}}) 198 | if ret != {{.Ret.GoData}} { 199 | t.Fatalf("Expected %v, but got %v\n", {{.Ret.GoData}}, ret) 200 | }{{end}} 201 | {{.CgoPost}} 202 | } 203 | {{if .Bench}} 204 | func {{.BenchmarkName}}(b *testing.B) { 205 | {{.CgoPre}} 206 | b.ResetTimer() 207 | for i := 0; i < b.N; i++ { 208 | {{.Name}}({{.Arguments.Value}}) 209 | } 210 | } 211 | {{end}} 212 | {{end}} 213 | `)) 214 | 215 | var testingNOCGO = template.Must(template.New("testingNOCGO").Parse(`package testlib 216 | 217 | import ( 218 | "log" 219 | "os" 220 | "runtime" 221 | "testing" 222 | 223 | "github.com/notti/nocgo" 224 | ) 225 | 226 | {{range .}} 227 | 228 | var {{.Name}}Func func{{.NOCGO}} 229 | 230 | {{if .Multitest}}func {{.TestName}}Multi(t *testing.T) { 231 | for i:=0; i < 100; i++ { 232 | t.Run("{{.TestName}}Multi", func(t *testing.T) { 233 | t.Parallel() 234 | {{.NOCGOPre}} 235 | {{if not .Ret.Void}}ret := {{end}}{{.Name}}Func({{.DataInit}}){{if not .Ret.Void}} 236 | if ret != {{.Ret.GoData}} { 237 | t.Fatalf("Expected %v, but got %v\n", {{.Ret.GoData}}, ret) 238 | }{{end}} 239 | {{.NOCGOPost}} 240 | }) 241 | } 242 | } 243 | {{else}}func {{.TestName}}(t *testing.T) { 244 | {{.NOCGOPre}} 245 | {{if not .Ret.Void}}ret := {{end}}{{.Name}}Func({{.DataInit}}){{if not .Ret.Void}} 246 | if ret != {{.Ret.GoData}} { 247 | t.Fatalf("Expected %v, but got %v\n", {{.Ret.GoData}}, ret) 248 | }{{end}} 249 | {{.NOCGOPost}} 250 | }{{end}} 251 | {{if .Bench}} 252 | func {{.BenchmarkName}}(b *testing.B) { 253 | {{.NOCGOPre}} 254 | b.ResetTimer() 255 | for i := 0; i < b.N; i++ { 256 | {{.Name}}Func({{.DataInit}}) 257 | } 258 | } 259 | {{end}}{{end}} 260 | 261 | func TestMain(m *testing.M) { 262 | var lib string 263 | switch runtime.GOARCH { 264 | case "386": 265 | lib = "libcalltest32.so.1" 266 | case "amd64": 267 | lib = "libcalltest64.so.1" 268 | default: 269 | log.Fatalln("Unknown arch ", runtime.GOARCH) 270 | } 271 | 272 | l, err := nocgo.Open(lib) 273 | if err != nil { 274 | log.Fatal(err) 275 | } 276 | 277 | {{range .}}if err := l.Func("{{.Name}}", &{{.Name}}Func); err != nil { 278 | log.Fatal(err) 279 | } 280 | 281 | {{end}} 282 | 283 | os.Exit(m.Run()) 284 | } 285 | `)) 286 | 287 | func main() { 288 | cfile, err := os.Create("testlib/test.c") 289 | if err != nil { 290 | log.Fatalln("Couldn't open c file") 291 | } 292 | bridgefile, err := os.Create("testlib/cgo_bridge.go") 293 | if err != nil { 294 | log.Fatalln("Couldn't open bridge file") 295 | } 296 | testingCgofile, err := os.Create("testlib/cgo_test.go") 297 | if err != nil { 298 | log.Fatalln("Couldn't open bridge file") 299 | } 300 | testingNOCGOfile, err := os.Create("nocgo/nocgo_test.go") 301 | if err != nil { 302 | log.Fatalln("Couldn't open bridge file") 303 | } 304 | tests := []Test{ 305 | { 306 | Name: "empty", 307 | Ret: Value{void, nil, nil}, 308 | Bench: true, 309 | }, 310 | { 311 | Name: "int1", 312 | Ret: Value{i8, "10", "10"}, 313 | }, 314 | { 315 | Name: "int2", 316 | Ret: Value{i8, "-10", "-10"}, 317 | }, 318 | { 319 | Name: "int3", 320 | Ret: Value{u8, "10", "10"}, 321 | }, 322 | { 323 | Name: "int4", 324 | Ret: Value{u8, "-10", "246"}, 325 | }, 326 | { 327 | Name: "int5", 328 | Ret: Value{u8, "a+b", "44"}, 329 | Arguments: Arguments{ 330 | {"a", Value{u8, "100", nil}}, 331 | {"b", Value{u8, "200", nil}}, 332 | }, 333 | }, 334 | { 335 | Name: "int6", 336 | Ret: Value{u64, "a", "100"}, 337 | Arguments: Arguments{ 338 | {"a", Value{u8, "100", nil}}, 339 | }, 340 | }, 341 | { 342 | Name: "int7", 343 | Ret: Value{u8, "a", "100"}, 344 | Arguments: Arguments{ 345 | {"a", Value{u64, "100", nil}}, 346 | }, 347 | }, 348 | { 349 | Name: "intBig1", 350 | Ret: Value{u64, "81985529216486895", "uint64(81985529216486895)"}, 351 | }, 352 | { 353 | Name: "intBig2", 354 | Ret: Value{u64, "a", "uint64(81985529216486895)"}, 355 | Arguments: Arguments{ 356 | {"a", Value{u64, "81985529216486895", nil}}, 357 | }, 358 | }, 359 | { 360 | Name: "float1", 361 | Ret: Value{f32, "10.5", "10.5"}, 362 | }, 363 | { 364 | Name: "float2", 365 | Ret: Value{f64, "10.5", "10.5"}, 366 | Bench: true, 367 | }, 368 | { 369 | Name: "stackSpill1", 370 | Ret: Value{i8, "a+b+c+d+e+f+g+h", "8"}, 371 | Arguments: Arguments{ 372 | {"a", Value{i8, "1", nil}}, 373 | {"b", Value{i8, "1", nil}}, 374 | {"c", Value{i8, "1", nil}}, 375 | {"d", Value{i8, "1", nil}}, 376 | {"e", Value{i8, "1", nil}}, 377 | {"f", Value{i8, "1", nil}}, 378 | {"g", Value{i8, "1", nil}}, 379 | {"h", Value{i8, "1", nil}}, 380 | }, 381 | }, 382 | { 383 | Name: "stackSpill2", 384 | Ret: Value{f32, "a+b+c+d+e+f+g+h+i+j", "10"}, 385 | Arguments: Arguments{ 386 | {"a", Value{f32, "1", nil}}, 387 | {"b", Value{f32, "1", nil}}, 388 | {"c", Value{f32, "1", nil}}, 389 | {"d", Value{f32, "1", nil}}, 390 | {"e", Value{f32, "1", nil}}, 391 | {"f", Value{f32, "1", nil}}, 392 | {"g", Value{f32, "1", nil}}, 393 | {"h", Value{f32, "1", nil}}, 394 | {"i", Value{f32, "1", nil}}, 395 | {"j", Value{f32, "1", nil}}, 396 | }, 397 | }, 398 | { 399 | Name: "stackSpill3", 400 | Ret: Value{i8, "ia+ib+ic+id+ie+f+ig+ih+fa+fb+fc+fd+fe+ff+fg+fh+fi+fj", "18"}, 401 | Arguments: Arguments{ 402 | {"ia", Value{i8, "1", nil}}, 403 | {"ib", Value{i8, "1", nil}}, 404 | {"ic", Value{i8, "1", nil}}, 405 | {"id", Value{i8, "1", nil}}, 406 | {"ie", Value{i8, "1", nil}}, 407 | {"f", Value{i8, "1", nil}}, 408 | {"ig", Value{i8, "1", nil}}, 409 | {"ih", Value{i8, "1", nil}}, 410 | {"fa", Value{f32, "1", nil}}, 411 | {"fb", Value{f32, "1", nil}}, 412 | {"fc", Value{f32, "1", nil}}, 413 | {"fd", Value{f32, "1", nil}}, 414 | {"fe", Value{f32, "1", nil}}, 415 | {"ff", Value{f32, "1", nil}}, 416 | {"fg", Value{f32, "1", nil}}, 417 | {"fh", Value{f32, "1", nil}}, 418 | {"fi", Value{f32, "1", nil}}, 419 | {"fj", Value{f32, "1", nil}}, 420 | }, 421 | Bench: true, 422 | }, 423 | { 424 | Name: "stackSpill4", 425 | Ret: Value{i8, "ia+ib+ic+id+ie+f+ig+ih+fa+fb+fc+fd+fe+ff+fg+fh+fi+fj", "18"}, 426 | Arguments: Arguments{ 427 | {"ia", Value{i8, "1", nil}}, 428 | {"fa", Value{f32, "1", nil}}, 429 | {"ib", Value{i8, "1", nil}}, 430 | {"fb", Value{f32, "1", nil}}, 431 | {"ic", Value{i8, "1", nil}}, 432 | {"fc", Value{f32, "1", nil}}, 433 | {"id", Value{i8, "1", nil}}, 434 | {"fd", Value{f32, "1", nil}}, 435 | {"ie", Value{i8, "1", nil}}, 436 | {"fe", Value{f32, "1", nil}}, 437 | {"f", Value{i8, "1", nil}}, 438 | {"ff", Value{f32, "1", nil}}, 439 | {"ig", Value{i8, "1", nil}}, 440 | {"fg", Value{f32, "1", nil}}, 441 | {"ih", Value{i8, "1", nil}}, 442 | {"fh", Value{f32, "1", nil}}, 443 | {"fi", Value{f32, "1", nil}}, 444 | {"fj", Value{f32, "1", nil}}, 445 | }, 446 | }, 447 | { 448 | Name: "funcall1", 449 | Ret: Value{i32, `sprintf(s, "test from C: %d %1.1f %s\n", a, b, c)`, "27"}, 450 | Arguments: Arguments{ 451 | {"s", Value{byteSlice, nil, "buf"}}, 452 | {"a", Value{i8, "-1", nil}}, 453 | {"b", Value{f32, "1.5", nil}}, 454 | {"c", Value{byteSlice, nil, `[]byte("gotest\000")`}}, 455 | }, 456 | CgoPre: "buf := make([]byte, 1024)", 457 | CgoPost: ` if string(buf[:ret]) != "test from C: -1 1.5 gotest\n" { 458 | t.Fatalf("Expected \"test from C: -1 1.5 gotest\n\", but got \"%s\"", string(buf[:ret])) 459 | }`, 460 | NOCGOPre: "buf := make([]byte, 1024)", 461 | NOCGOPost: ` if string(buf[:ret]) != "test from C: -1 1.5 gotest\n" { 462 | t.Fatalf("Expected \"test from C: -1 1.5 gotest\n\", but got \"%s\"", string(buf[:ret])) 463 | }`, 464 | Multitest: true, 465 | }, 466 | } 467 | if err := ccode.Execute(cfile, tests); err != nil { 468 | log.Fatal(err) 469 | } 470 | cfile.Close() 471 | if err := bridge.Execute(bridgefile, tests); err != nil { 472 | log.Fatal(err) 473 | } 474 | bridgefile.Close() 475 | if err := testingCgo.Execute(testingCgofile, tests); err != nil { 476 | log.Fatal(err) 477 | } 478 | testingCgofile.Close() 479 | if err := testingNOCGO.Execute(testingNOCGOfile, tests); err != nil { 480 | log.Fatal(err) 481 | } 482 | testingNOCGOfile.Close() 483 | } 484 | -------------------------------------------------------------------------------- /steps/1_dynamic/rewrite.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "debug/elf" 6 | "encoding/binary" 7 | "errors" 8 | "io" 9 | "log" 10 | "os" 11 | "sort" 12 | ) 13 | 14 | const ( 15 | in = "a.out" 16 | out = "dyn" 17 | interp = "/lib64/ld-linux-x86-64.so.2" 18 | ) 19 | 20 | var libs = []string{"libc.so.6"} 21 | var relocations = map[string]string{ 22 | "__printf": "puts", 23 | } 24 | 25 | func doCopy() { 26 | src, err := os.Open(in) 27 | if err != nil { 28 | log.Fatal("couldn't open", in, ":", err) 29 | } 30 | defer src.Close() 31 | dst, err := os.OpenFile(out, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0777) 32 | if err != nil { 33 | log.Fatal("couldn't create", out, ":", err) 34 | } 35 | defer dst.Close() 36 | _, err = io.Copy(dst, src) 37 | if err != nil { 38 | log.Fatal("couldn't copy:", err) 39 | } 40 | } 41 | 42 | type elfFile struct { 43 | f *os.File 44 | e *elf.File 45 | phoff uint64 46 | phentsize uint64 47 | shoff uint64 48 | shentsize uint64 49 | shstrndx uint64 50 | buffer [1024]byte 51 | } 52 | 53 | func openElfFile(name string) (*elfFile, error) { 54 | ret := &elfFile{} 55 | var err error 56 | ret.f, err = os.OpenFile(name, os.O_RDWR, 0777) 57 | if err != nil { 58 | return nil, err 59 | } 60 | ret.e, err = elf.NewFile(ret.f) 61 | if err != nil { 62 | return nil, err 63 | } 64 | 65 | _, err = ret.Seek(0, io.SeekCurrent) 66 | if err != nil { 67 | return nil, err 68 | } 69 | 70 | switch ret.e.Class { 71 | case elf.ELFCLASS32: 72 | var hdr elf.Header32 73 | err = ret.ReadData(&hdr) 74 | if err != nil { 75 | return nil, err 76 | } 77 | ret.phoff = uint64(hdr.Phoff) 78 | ret.phentsize = uint64(hdr.Phentsize) 79 | ret.shoff = uint64(hdr.Shoff) 80 | ret.shentsize = uint64(hdr.Shentsize) 81 | ret.shstrndx = uint64(hdr.Shstrndx) 82 | case elf.ELFCLASS64: 83 | var hdr elf.Header64 84 | err = ret.ReadData(&hdr) 85 | if err != nil { 86 | return nil, err 87 | } 88 | ret.phoff = hdr.Phoff 89 | ret.phentsize = uint64(hdr.Phentsize) 90 | ret.shoff = hdr.Shoff 91 | ret.shentsize = uint64(hdr.Shentsize) 92 | ret.shstrndx = uint64(hdr.Shstrndx) 93 | } 94 | 95 | return ret, nil 96 | } 97 | 98 | func (e *elfFile) Seek(where int64, whence int) (int64, error) { 99 | return e.f.Seek(where, whence) 100 | } 101 | 102 | func (e *elfFile) ReadData(data interface{}) error { 103 | return binary.Read(e.f, e.e.ByteOrder, data) 104 | } 105 | 106 | func (e *elfFile) WriteData(data interface{}) error { 107 | return binary.Write(e.f, e.e.ByteOrder, data) 108 | } 109 | 110 | func (e *elfFile) Write(b []byte) error { 111 | _, err := e.f.Write(b) 112 | return err 113 | } 114 | 115 | func (e *elfFile) WriteAt(b []byte, off uint64) error { 116 | _, err := e.f.WriteAt(b, int64(off)) 117 | return err 118 | } 119 | 120 | func (e *elfFile) Read8() (uint8, error) { 121 | _, err := e.f.Read(e.buffer[:1]) 122 | return e.buffer[0], err 123 | } 124 | 125 | func (e *elfFile) Read16() (uint16, error) { 126 | _, err := e.f.Read(e.buffer[:2]) 127 | return e.e.ByteOrder.Uint16(e.buffer[:]), err 128 | } 129 | 130 | func (e *elfFile) Read32() (uint32, error) { 131 | _, err := e.f.Read(e.buffer[:4]) 132 | return e.e.ByteOrder.Uint32(e.buffer[:]), err 133 | } 134 | 135 | func (e *elfFile) Read64() (uint64, error) { 136 | _, err := e.f.Read(e.buffer[:8]) 137 | return e.e.ByteOrder.Uint64(e.buffer[:]), err 138 | } 139 | 140 | func (e *elfFile) Read8At(where int64) (uint8, error) { 141 | _, err := e.f.ReadAt(e.buffer[:1], where) 142 | return e.buffer[0], err 143 | } 144 | 145 | func (e *elfFile) Read16At(where int64) (uint16, error) { 146 | _, err := e.f.ReadAt(e.buffer[:2], where) 147 | return e.e.ByteOrder.Uint16(e.buffer[:]), err 148 | } 149 | 150 | func (e *elfFile) Read32At(where int64) (uint32, error) { 151 | _, err := e.f.ReadAt(e.buffer[:4], where) 152 | return e.e.ByteOrder.Uint32(e.buffer[:]), err 153 | } 154 | 155 | func (e *elfFile) Read64At(where int64) (uint64, error) { 156 | _, err := e.f.ReadAt(e.buffer[:8], where) 157 | return e.e.ByteOrder.Uint64(e.buffer[:]), err 158 | } 159 | 160 | func (e *elfFile) Write8(data uint8) error { 161 | _, err := e.f.Write([]byte{data}) 162 | return err 163 | } 164 | 165 | func (e *elfFile) Write16(data uint16) error { 166 | e.e.ByteOrder.PutUint16(e.buffer[:], data) 167 | _, err := e.f.Write(e.buffer[:2]) 168 | return err 169 | } 170 | 171 | func (e *elfFile) Write32(data uint32) error { 172 | e.e.ByteOrder.PutUint32(e.buffer[:], data) 173 | _, err := e.f.Write(e.buffer[:4]) 174 | return err 175 | } 176 | 177 | func (e *elfFile) Write64(data uint64) error { 178 | e.e.ByteOrder.PutUint64(e.buffer[:], data) 179 | _, err := e.f.Write(e.buffer[:8]) 180 | return err 181 | } 182 | 183 | func (e *elfFile) Write8At(data uint8, where uint64) error { 184 | return e.WriteAt([]byte{data}, where) 185 | } 186 | 187 | func (e *elfFile) Write16At(data uint16, where uint64) error { 188 | e.e.ByteOrder.PutUint16(e.buffer[:], data) 189 | return e.WriteAt(e.buffer[:2], where) 190 | } 191 | 192 | func (e *elfFile) Write32At(data uint32, where uint64) error { 193 | e.e.ByteOrder.PutUint32(e.buffer[:], data) 194 | return e.WriteAt(e.buffer[:4], where) 195 | } 196 | 197 | func (e *elfFile) Write64At(data uint64, where uint64) error { 198 | e.e.ByteOrder.PutUint64(e.buffer[:], data) 199 | return e.WriteAt(e.buffer[:8], where) 200 | } 201 | 202 | func (e *elfFile) WriteSections() error { 203 | if _, err := e.f.Seek(int64(e.shoff), io.SeekStart); err != nil { 204 | return err 205 | } 206 | shstrtab := 0 207 | shstrtabpos := e.shoff 208 | names := make([]uint64, len(e.e.Sections)) 209 | for i, sec := range e.e.Sections { 210 | names[i] = e.shoff - shstrtabpos 211 | if err := e.Write(append([]byte(sec.Name), 0)); err != nil { 212 | return err 213 | } 214 | e.shoff += uint64(len(sec.Name)) + 1 215 | if sec.Name == ".shstrtab" { 216 | shstrtab = i 217 | } 218 | } 219 | e.e.Sections[shstrtab].Offset = shstrtabpos 220 | e.e.Sections[shstrtab].FileSize = e.shoff - shstrtabpos 221 | 222 | for i, sec := range e.e.Sections { 223 | if err := e.WriteSection(sec.SectionHeader, names[i]); err != nil { 224 | return err 225 | } 226 | } 227 | switch e.e.Class { 228 | case elf.ELFCLASS32: 229 | if err := e.Write32At(uint32(e.shoff), 0x20); err != nil { 230 | return err 231 | } 232 | if err := e.Write16At(uint16(len(e.e.Sections)), 0x30); err != nil { 233 | return err 234 | } 235 | case elf.ELFCLASS64: 236 | if err := e.Write64At(e.shoff, 0x28); err != nil { 237 | return err 238 | } 239 | if err := e.Write16At(uint16(len(e.e.Sections)), 0x3C); err != nil { 240 | return err 241 | } 242 | } 243 | return nil 244 | } 245 | 246 | func (e *elfFile) WriteSection(sh elf.SectionHeader, name uint64) error { 247 | switch e.e.Class { 248 | case elf.ELFCLASS32: 249 | hdr := elf.Section32{ 250 | Name: uint32(name), 251 | Type: uint32(sh.Type), 252 | Flags: uint32(sh.Flags), 253 | Addr: uint32(sh.Addr), 254 | Off: uint32(sh.Offset), 255 | Size: uint32(sh.FileSize), 256 | Link: sh.Link, 257 | Info: sh.Info, 258 | Addralign: uint32(sh.Addralign), 259 | Entsize: uint32(sh.Entsize), 260 | } 261 | return e.WriteData(hdr) 262 | case elf.ELFCLASS64: 263 | hdr := elf.Section64{ 264 | Name: uint32(name), 265 | Type: uint32(sh.Type), 266 | Flags: uint64(sh.Flags), 267 | Addr: sh.Addr, 268 | Off: sh.Offset, 269 | Size: sh.FileSize, 270 | Link: sh.Link, 271 | Info: sh.Info, 272 | Addralign: sh.Addralign, 273 | Entsize: sh.Entsize, 274 | } 275 | return e.WriteData(hdr) 276 | } 277 | // compression header not handeled 278 | return errors.New("Unknown elf bit size") 279 | } 280 | 281 | func (e *elfFile) WritePrograms() error { 282 | if _, err := e.Seek(int64(e.phoff), io.SeekStart); err != nil { 283 | return err 284 | } 285 | for _, prog := range e.e.Progs { 286 | if err := e.WriteProgram(prog.ProgHeader); err != nil { 287 | return err 288 | } 289 | } 290 | switch e.e.Class { 291 | case elf.ELFCLASS32: 292 | if err := e.Write32At(uint32(e.phoff), 0x1C); err != nil { 293 | return err 294 | } 295 | if err := e.Write16At(uint16(len(e.e.Progs)), 0x2C); err != nil { 296 | return err 297 | } 298 | case elf.ELFCLASS64: 299 | if err := e.Write64At(e.phoff, 0x20); err != nil { 300 | return err 301 | } 302 | if err := e.Write16At(uint16(len(e.e.Progs)), 0x38); err != nil { 303 | return err 304 | } 305 | } 306 | return nil 307 | } 308 | 309 | func (e *elfFile) WriteProgram(ph elf.ProgHeader) error { 310 | switch e.e.Class { 311 | case elf.ELFCLASS32: 312 | hdr := elf.Prog32{ 313 | Type: uint32(ph.Type), 314 | Flags: uint32(ph.Flags), 315 | Off: uint32(ph.Off), 316 | Vaddr: uint32(ph.Vaddr), 317 | Paddr: uint32(ph.Paddr), 318 | Filesz: uint32(ph.Filesz), 319 | Memsz: uint32(ph.Memsz), 320 | Align: uint32(ph.Align), 321 | } 322 | return e.WriteData(hdr) 323 | case elf.ELFCLASS64: 324 | hdr := elf.Prog64{ 325 | Type: uint32(ph.Type), 326 | Flags: uint32(ph.Flags), 327 | Off: ph.Off, 328 | Vaddr: ph.Vaddr, 329 | Paddr: ph.Paddr, 330 | Filesz: ph.Filesz, 331 | Memsz: ph.Memsz, 332 | Align: ph.Align, 333 | } 334 | return e.WriteData(hdr) 335 | } 336 | return errors.New("Unknown elf bit size") 337 | } 338 | 339 | func (e *elfFile) Read(b []byte) error { 340 | _, err := e.f.Read(b) 341 | return err 342 | } 343 | 344 | func (e *elfFile) Copy(src, dst int64, length int) error { 345 | buffer := make([]byte, length) 346 | _, err := e.Seek(src, io.SeekStart) 347 | if err != nil { 348 | return err 349 | } 350 | err = e.Read(buffer) 351 | if err != nil { 352 | return err 353 | } 354 | _, err = e.Seek(dst, io.SeekStart) 355 | if err != nil { 356 | return err 357 | } 358 | err = e.Write(buffer) 359 | if err != nil { 360 | return err 361 | } 362 | return nil 363 | } 364 | 365 | func (e *elfFile) Close() error { 366 | return e.f.Close() 367 | } 368 | 369 | // Dyn contains a single entry of the dynamic table 370 | type Dyn struct { 371 | Tag elf.DynTag 372 | Val uint64 373 | } 374 | 375 | func padding(addr, align uint64) uint64 { 376 | align1 := align - 1 377 | return (align - (addr & align1)) & align1 378 | } 379 | 380 | // DynSymbol represents a dynamic symbol 381 | type DynSymbol struct { 382 | Name string 383 | Value uint64 384 | Size uint64 385 | Bind elf.SymBind 386 | Type elf.SymType 387 | Vis elf.SymVis 388 | Section int 389 | } 390 | 391 | func (e *elfFile) makeDynsym(elements []DynSymbol) (dynsym, dynstr []byte) { 392 | sym := &bytes.Buffer{} 393 | str := &bytes.Buffer{} 394 | for _, elem := range elements { 395 | namei := str.Len() 396 | str.Write(append([]byte(elem.Name), 0)) 397 | switch e.e.Class { 398 | case elf.ELFCLASS32: 399 | binary.Write(sym, e.e.ByteOrder, elf.Sym32{ 400 | Name: uint32(namei), 401 | Value: uint32(elem.Value), 402 | Size: uint32(elem.Size), 403 | Info: byte(elem.Bind)<<4 | byte(elem.Type)&0x0f, 404 | Other: byte(elem.Vis) & 0x03, 405 | Shndx: uint16(elem.Section), 406 | }) 407 | case elf.ELFCLASS64: 408 | binary.Write(sym, e.e.ByteOrder, elf.Sym64{ 409 | Name: uint32(namei), 410 | Value: uint64(elem.Value), 411 | Size: uint64(elem.Size), 412 | Info: byte(elem.Bind)<<4 | byte(elem.Type)&0x0f, 413 | Other: byte(elem.Vis) & 0x03, 414 | Shndx: uint16(elem.Section), 415 | }) 416 | } 417 | } 418 | if str.Len() == 0 { 419 | str.WriteByte(0) 420 | } 421 | return sym.Bytes(), str.Bytes() 422 | } 423 | 424 | func (e *elfFile) makeDynsec(elements []Dyn) []byte { 425 | ret := &bytes.Buffer{} 426 | switch e.e.Class { 427 | case elf.ELFCLASS32: 428 | var secs []elf.Dyn32 429 | for _, sec := range elements { 430 | secs = append(secs, elf.Dyn32{ 431 | Tag: int32(sec.Tag), 432 | Val: uint32(sec.Val), 433 | }) 434 | } 435 | binary.Write(ret, e.e.ByteOrder, secs) 436 | case elf.ELFCLASS64: 437 | var secs []elf.Dyn64 438 | for _, sec := range elements { 439 | secs = append(secs, elf.Dyn64{ 440 | Tag: int64(sec.Tag), 441 | Val: uint64(sec.Val), 442 | }) 443 | } 444 | binary.Write(ret, e.e.ByteOrder, secs) 445 | } 446 | return ret.Bytes() 447 | } 448 | 449 | // RelSymbol represents a symbol in need of relocation 450 | type RelSymbol struct { 451 | Off uint64 452 | SymNo uint64 453 | } 454 | 455 | func (e *elfFile) makeDynRel(symbols []RelSymbol) ([]byte, bool, uint64) { 456 | ret := &bytes.Buffer{} 457 | var rela bool 458 | var relt uint64 459 | switch e.e.Machine { 460 | case elf.EM_386: 461 | rela = false 462 | relt = uint64(elf.R_386_JMP_SLOT) 463 | case elf.EM_X86_64: 464 | rela = true 465 | relt = uint64(elf.R_X86_64_JMP_SLOT) 466 | default: 467 | log.Panic("Unknown machine type ", e.e.Machine) 468 | } 469 | 470 | var relsz uint64 471 | 472 | switch e.e.Class { 473 | case elf.ELFCLASS32: 474 | if rela { 475 | for _, symbol := range symbols { 476 | binary.Write(ret, e.e.ByteOrder, elf.Rela32{ 477 | Off: uint32(symbol.Off), 478 | Info: uint32(symbol.SymNo<<8 | relt), 479 | }) 480 | } 481 | relsz = 12 482 | } else { 483 | for _, symbol := range symbols { 484 | 485 | binary.Write(ret, e.e.ByteOrder, elf.Rel32{ 486 | Off: uint32(symbol.Off), 487 | Info: uint32(symbol.SymNo<<8 | relt), 488 | }) 489 | } 490 | relsz = 8 491 | } 492 | case elf.ELFCLASS64: 493 | if rela { 494 | for _, symbol := range symbols { 495 | binary.Write(ret, e.e.ByteOrder, elf.Rela64{ 496 | Off: symbol.Off, 497 | Info: symbol.SymNo<<32 | relt, 498 | }) 499 | } 500 | relsz = 24 501 | } else { 502 | for _, symbol := range symbols { 503 | binary.Write(ret, e.e.ByteOrder, elf.Rel64{ 504 | Off: symbol.Off, 505 | Info: symbol.SymNo<<32 | relt, 506 | }) 507 | } 508 | relsz = 16 509 | } 510 | } 511 | return ret.Bytes(), rela, relsz 512 | } 513 | 514 | func main() { 515 | doCopy() 516 | f, err := openElfFile(out) 517 | if err != nil { 518 | log.Fatal(err) 519 | } 520 | 521 | /* 522 | KEEP EXEC (we are not dyn after all) 523 | try to put new program headers, dyn, interp into first 4k 524 | 0 -+-----------------------------------+-- 525 | | ELF | 526 | +-----------------------------------+ 527 | | program headers | 528 | +-----------------------------------+ 529 | | interp | 530 | +-----------------------------------+ 531 | | dyn stuff | 532 | +-----------------------------------+ 533 | | other stuff that needs relocation | 534 | +-----------------------------------+-- ensure mapping until here 535 | +-----------------------------------+ 536 | entry -> | Everything else (e.g., text) | 537 | +-----------------------------------+ 538 | | .shstrtab | 539 | +-----------------------------------+ 540 | | Section headers | 541 | +-----------------------------------+ 542 | */ 543 | 544 | // First some sanity checks - and checks if we can do our meddling, after all we don't support everything in this POC 545 | 546 | if f.e.Type != elf.ET_EXEC { 547 | log.Panic("only static binaries not using an interp supported") 548 | } 549 | 550 | var base uint64 551 | var baseProg int 552 | 553 | for i, prog := range f.e.Progs { 554 | if prog.Type == elf.PT_INTERP || prog.Type == elf.PT_DYNAMIC { 555 | log.Panic("only static binaries not using an interp supported") 556 | } 557 | if prog.Type == elf.PT_LOAD { 558 | if base == 0 { 559 | base = prog.Vaddr 560 | baseProg = i 561 | } else if prog.Vaddr < base { 562 | base = prog.Vaddr 563 | baseProg = i 564 | } 565 | } 566 | } 567 | 568 | if uint64(f.phoff+f.phentsize*uint64(len(f.e.Progs))) > f.e.Entry { 569 | log.Panic("Not enough space before entry point") 570 | } 571 | 572 | interpProg := len(f.e.Progs) 573 | 574 | f.e.Progs = append(f.e.Progs, &elf.Prog{ 575 | ProgHeader: elf.ProgHeader{ 576 | Type: elf.PT_INTERP, 577 | Flags: elf.PF_R, 578 | Off: 0, // fill later 579 | Vaddr: 0, // fill later 580 | Paddr: 0, // fill later 581 | Filesz: 0, // fill later 582 | Memsz: 0, // fill later 583 | Align: 1, 584 | }}) 585 | 586 | dynsecProg := len(f.e.Progs) 587 | 588 | f.e.Progs = append(f.e.Progs, &elf.Prog{ 589 | ProgHeader: elf.ProgHeader{ 590 | Type: elf.PT_DYNAMIC, 591 | Flags: elf.PF_R | elf.PF_W, 592 | Off: 0, // fill later 593 | Vaddr: 0, // fill later 594 | Paddr: 0, // fill later 595 | Filesz: 0, // fill later 596 | Memsz: 0, // fill later 597 | Align: 8, 598 | }}) 599 | 600 | interpPos := f.phoff + f.phentsize*uint64(len(f.e.Progs)) 601 | interpB := append([]byte(interp), 0) 602 | interpLen := uint64(len(interpB)) 603 | 604 | f.e.Progs[interpProg].Off = interpPos 605 | f.e.Progs[interpProg].Vaddr = interpPos + base 606 | f.e.Progs[interpProg].Paddr = interpPos + base 607 | f.e.Progs[interpProg].Filesz = interpLen 608 | f.e.Progs[interpProg].Memsz = interpLen 609 | 610 | hashPos := interpPos + interpLen 611 | hashPos += padding(hashPos, 8) 612 | hash := make([]byte, 8*4) // Empty 64bit DT_HASH 613 | hashLen := uint64(len(hash)) 614 | 615 | var relList []RelSymbol 616 | 617 | var symsection int 618 | 619 | symbolList, err := f.e.Symbols() 620 | if err != nil { 621 | log.Fatal(err) 622 | } 623 | 624 | var symdefs []DynSymbol 625 | 626 | symdefs = append(symdefs, DynSymbol{ 627 | Name: "", 628 | Value: 0, 629 | Size: 0, 630 | Bind: elf.STB_LOCAL, 631 | Type: elf.STT_NOTYPE, 632 | Vis: elf.STV_DEFAULT, 633 | Section: int(elf.SHN_UNDEF), 634 | }) 635 | 636 | for _, sym := range symbolList { 637 | if dynsym, ok := relocations[sym.Name]; ok { 638 | symsection = int(sym.Section) 639 | relList = append(relList, RelSymbol{ 640 | Off: sym.Value, 641 | SymNo: uint64(len(symdefs)), 642 | }) 643 | symdefs = append(symdefs, DynSymbol{ 644 | Name: dynsym, 645 | Value: 0, 646 | Size: 0, 647 | Bind: elf.STB_GLOBAL, 648 | Type: elf.STT_FUNC, 649 | Vis: elf.STV_DEFAULT, 650 | Section: int(elf.SHN_UNDEF), 651 | }) 652 | } 653 | } 654 | 655 | dynsym, dynstr := f.makeDynsym(symdefs) 656 | 657 | var libOffsets []uint64 658 | 659 | for _, l := range libs { 660 | libOffsets = append(libOffsets, uint64(len(dynstr))) 661 | dynstr = append(dynstr, []byte(l)...) 662 | dynstr = append(dynstr, 0) 663 | } 664 | 665 | dynsymLocal := 0 666 | dynstrPos := hashPos + hashLen 667 | dynstrLen := uint64(len(dynstr)) 668 | 669 | dynsymPos := dynstrPos + dynstrLen 670 | dynsymPos += padding(dynsymPos, 8) 671 | dynsymLen := uint64(len(dynsym)) 672 | 673 | // TODO: DT_BIND_NOW? 674 | 675 | dynrel, rela, relsz := f.makeDynRel(relList) 676 | dynrelPos := dynsymPos + dynsymLen 677 | dynrelPos += padding(dynrelPos, 8) 678 | dynrelLen := uint64(len(dynrel)) 679 | 680 | var dynsecs []Dyn 681 | for _, offset := range libOffsets { 682 | dynsecs = append(dynsecs, Dyn{Tag: elf.DT_NEEDED, Val: uint64(offset)}) 683 | } 684 | 685 | if rela { 686 | dynsecs = append(dynsecs, Dyn{Tag: elf.DT_RELA, Val: uint64(base + dynrelPos)}) 687 | dynsecs = append(dynsecs, Dyn{Tag: elf.DT_RELASZ, Val: uint64(dynrelLen)}) 688 | dynsecs = append(dynsecs, Dyn{Tag: elf.DT_RELAENT, Val: uint64(relsz)}) 689 | } else { 690 | dynsecs = append(dynsecs, Dyn{Tag: elf.DT_REL, Val: uint64(base + dynrelPos)}) 691 | dynsecs = append(dynsecs, Dyn{Tag: elf.DT_RELSZ, Val: uint64(dynrelLen)}) 692 | dynsecs = append(dynsecs, Dyn{Tag: elf.DT_RELENT, Val: uint64(relsz)}) 693 | } 694 | 695 | dynsecs = append(dynsecs, []Dyn{ 696 | {Tag: elf.DT_STRTAB, Val: base + dynstrPos}, 697 | {Tag: elf.DT_STRSZ, Val: dynstrLen}, 698 | {Tag: elf.DT_SYMTAB, Val: base + dynsymPos}, 699 | {Tag: elf.DT_SYMENT, Val: dynsymLen}, 700 | {Tag: elf.DT_HASH, Val: hashPos + base}, 701 | {Tag: elf.DT_NULL, Val: 0}, 702 | }...) 703 | 704 | dynsec := f.makeDynsec(dynsecs) 705 | dynsecPos := dynrelPos + dynrelLen 706 | dynsecPos += padding(dynsecPos, 8) 707 | dynsecLen := uint64(len(dynsec)) 708 | 709 | f.e.Progs[dynsecProg].Off = dynsecPos 710 | f.e.Progs[dynsecProg].Vaddr = dynsecPos + base 711 | f.e.Progs[dynsecProg].Paddr = dynsecPos + base 712 | f.e.Progs[dynsecProg].Filesz = dynsecLen 713 | f.e.Progs[dynsecProg].Memsz = dynsecLen 714 | 715 | afterDynsec := dynsecPos + dynsecLen 716 | 717 | relPos := afterDynsec 718 | var torelocate []*elf.Section 719 | relocated := make(map[int]bool) 720 | 721 | for { 722 | var newRelocate []*elf.Section 723 | for i, sec := range f.e.Sections { 724 | if sec.Type == elf.SHT_NULL { 725 | continue 726 | } 727 | if sec.Offset < relPos && !relocated[i] { 728 | newRelocate = append(newRelocate, sec) 729 | relocated[i] = true 730 | } 731 | } 732 | if len(newRelocate) == 0 { 733 | break 734 | } 735 | torelocate = append(torelocate, newRelocate...) 736 | 737 | sort.Slice(torelocate, func(i, j int) bool { return torelocate[i].Offset < torelocate[j].Offset }) 738 | relPos = afterDynsec 739 | for _, sec := range torelocate { 740 | relPos += sec.Size 741 | if sec.Addralign > 1 { 742 | relPos += padding(relPos, sec.Addralign) 743 | } 744 | } 745 | } 746 | 747 | for _, sec := range torelocate { 748 | data := make([]byte, sec.Size) 749 | if _, err := f.f.ReadAt(data, int64(sec.Offset)); err != nil { 750 | log.Fatal(err) 751 | } 752 | if sec.Addralign > 1 { 753 | afterDynsec += padding(afterDynsec, sec.Addralign) 754 | } 755 | if err := f.WriteAt(data, afterDynsec); err != nil { 756 | log.Fatal(err) 757 | } 758 | for _, prog := range f.e.Progs { 759 | if prog.Off == sec.Offset { 760 | prog.Off = afterDynsec 761 | } 762 | if prog.Vaddr == sec.Offset+base { 763 | prog.Vaddr = afterDynsec + base 764 | prog.Paddr = afterDynsec + base 765 | } 766 | } 767 | 768 | sec.Addr += afterDynsec - sec.Offset // or base + offset 769 | sec.Offset, afterDynsec = afterDynsec, afterDynsec+sec.Offset 770 | } 771 | 772 | if afterDynsec > f.e.Entry { 773 | log.Fatal("not enough space before entry point") 774 | } 775 | 776 | if f.e.Progs[baseProg].Filesz < afterDynsec { 777 | f.e.Progs[baseProg].Filesz = afterDynsec 778 | f.e.Progs[baseProg].Memsz = afterDynsec 779 | } 780 | 781 | if err := f.WritePrograms(); err != nil { 782 | log.Fatal(err) 783 | } 784 | 785 | if err := f.WriteAt(interpB, interpPos); err != nil { 786 | log.Fatal(err) 787 | } 788 | 789 | if err := f.WriteAt(dynstr, dynstrPos); err != nil { 790 | log.Fatal(err) 791 | } 792 | 793 | if err := f.WriteAt(dynsym, dynsymPos); err != nil { 794 | log.Fatal(err) 795 | } 796 | 797 | if err := f.WriteAt(dynrel, dynrelPos); err != nil { 798 | log.Fatal(err) 799 | } 800 | 801 | if err := f.WriteAt(dynsec, dynsecPos); err != nil { 802 | log.Fatal(err) 803 | } 804 | 805 | f.e.Sections = append(f.e.Sections, &elf.Section{ 806 | SectionHeader: elf.SectionHeader{ 807 | Name: ".interp", 808 | Type: elf.SHT_PROGBITS, 809 | Flags: elf.SHF_ALLOC, 810 | Addr: base + interpPos, 811 | Offset: interpPos, 812 | FileSize: interpLen, 813 | Addralign: 1, 814 | }}) 815 | 816 | dynstrI := len(f.e.Sections) 817 | 818 | f.e.Sections = append(f.e.Sections, &elf.Section{ 819 | SectionHeader: elf.SectionHeader{ 820 | Name: ".dynstr", 821 | Type: elf.SHT_STRTAB, 822 | Flags: elf.SHF_ALLOC, 823 | Addr: base + dynstrPos, 824 | Offset: dynstrPos, 825 | FileSize: dynstrLen, 826 | Addralign: 1, 827 | }}) 828 | 829 | entSize := uint64(24) 830 | if f.e.Class == elf.ELFCLASS32 { 831 | entSize = 16 832 | } 833 | 834 | dynsymSec := len(f.e.Sections) 835 | 836 | f.e.Sections = append(f.e.Sections, &elf.Section{ 837 | SectionHeader: elf.SectionHeader{ 838 | Name: ".dynsym", 839 | Type: elf.SHT_DYNSYM, 840 | Flags: elf.SHF_ALLOC, 841 | Addr: base + dynsymPos, 842 | Offset: dynsymPos, 843 | FileSize: dynsymLen, 844 | Addralign: 8, 845 | Link: uint32(dynstrI), 846 | Entsize: entSize, 847 | Info: uint32(dynsymLocal + 1), 848 | }}) 849 | 850 | entSize = uint64(16) 851 | if f.e.Class == elf.ELFCLASS32 { 852 | entSize = 8 853 | } 854 | 855 | f.e.Sections = append(f.e.Sections, &elf.Section{ 856 | SectionHeader: elf.SectionHeader{ 857 | Name: ".dynamic", 858 | Type: elf.SHT_DYNAMIC, 859 | Flags: elf.SHF_ALLOC | elf.SHF_WRITE, 860 | Addr: base + dynsecPos, 861 | Offset: dynsecPos, 862 | FileSize: dynsecLen, 863 | Addralign: 8, 864 | Link: uint32(dynstrI), 865 | Entsize: entSize, 866 | }}) 867 | 868 | dynname := ".rel" 869 | if rela { 870 | dynname = ".rela" 871 | } 872 | dynname += f.e.Sections[symsection].Name 873 | 874 | shtype := elf.SHT_REL 875 | if rela { 876 | shtype = elf.SHT_RELA 877 | } 878 | 879 | f.e.Sections = append(f.e.Sections, &elf.Section{ 880 | SectionHeader: elf.SectionHeader{ 881 | Name: dynname, 882 | Type: shtype, 883 | Flags: elf.SHF_ALLOC, 884 | Addr: base + dynrelPos, 885 | Offset: dynrelPos, 886 | FileSize: dynrelLen, 887 | Addralign: 8, 888 | Link: uint32(dynsymSec), 889 | Info: uint32(symsection), 890 | Entsize: relsz, 891 | }}) 892 | 893 | f.e.Sections = append(f.e.Sections, &elf.Section{ 894 | SectionHeader: elf.SectionHeader{ 895 | Name: ".hash", 896 | Type: elf.SHT_HASH, 897 | Flags: elf.SHF_ALLOC, 898 | Addr: base + hashPos, 899 | Offset: hashPos, 900 | FileSize: hashLen, 901 | Addralign: 8, 902 | Link: uint32(dynsymSec), 903 | }}) 904 | 905 | shoff, err := f.f.Seek(0, io.SeekEnd) 906 | if err != nil { 907 | log.Fatal(err) 908 | } 909 | f.shoff = uint64(shoff) 910 | 911 | if err := f.WriteSections(); err != nil { 912 | log.Fatal(err) 913 | } 914 | 915 | f.Close() 916 | } 917 | -------------------------------------------------------------------------------- /steps/2_go/rewrite.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "debug/elf" 6 | "encoding/binary" 7 | "errors" 8 | "io" 9 | "log" 10 | "os" 11 | "sort" 12 | "strings" 13 | ) 14 | 15 | const ( 16 | in = "main" 17 | out = "dyn" 18 | interp = "/lib64/ld-linux-x86-64.so.2" 19 | ) 20 | 21 | var libs = []string{"libc.so.6"} 22 | 23 | func doCopy() { 24 | src, err := os.Open(in) 25 | if err != nil { 26 | log.Fatal("couldn't open", in, ":", err) 27 | } 28 | defer src.Close() 29 | dst, err := os.OpenFile(out, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0777) 30 | if err != nil { 31 | log.Fatal("couldn't create", out, ":", err) 32 | } 33 | defer dst.Close() 34 | _, err = io.Copy(dst, src) 35 | if err != nil { 36 | log.Fatal("couldn't copy:", err) 37 | } 38 | } 39 | 40 | type elfFile struct { 41 | f *os.File 42 | e *elf.File 43 | phoff uint64 44 | phentsize uint64 45 | shoff uint64 46 | shentsize uint64 47 | shstrndx uint64 48 | buffer [1024]byte 49 | } 50 | 51 | func openElfFile(name string) (*elfFile, error) { 52 | ret := &elfFile{} 53 | var err error 54 | ret.f, err = os.OpenFile(name, os.O_RDWR, 0777) 55 | if err != nil { 56 | return nil, err 57 | } 58 | ret.e, err = elf.NewFile(ret.f) 59 | if err != nil { 60 | return nil, err 61 | } 62 | 63 | _, err = ret.Seek(0, io.SeekCurrent) 64 | if err != nil { 65 | return nil, err 66 | } 67 | 68 | switch ret.e.Class { 69 | case elf.ELFCLASS32: 70 | var hdr elf.Header32 71 | err = ret.ReadData(&hdr) 72 | if err != nil { 73 | return nil, err 74 | } 75 | ret.phoff = uint64(hdr.Phoff) 76 | ret.phentsize = uint64(hdr.Phentsize) 77 | ret.shoff = uint64(hdr.Shoff) 78 | ret.shentsize = uint64(hdr.Shentsize) 79 | ret.shstrndx = uint64(hdr.Shstrndx) 80 | case elf.ELFCLASS64: 81 | var hdr elf.Header64 82 | err = ret.ReadData(&hdr) 83 | if err != nil { 84 | return nil, err 85 | } 86 | ret.phoff = hdr.Phoff 87 | ret.phentsize = uint64(hdr.Phentsize) 88 | ret.shoff = hdr.Shoff 89 | ret.shentsize = uint64(hdr.Shentsize) 90 | ret.shstrndx = uint64(hdr.Shstrndx) 91 | } 92 | 93 | return ret, nil 94 | } 95 | 96 | func (e *elfFile) Seek(where int64, whence int) (int64, error) { 97 | return e.f.Seek(where, whence) 98 | } 99 | 100 | func (e *elfFile) ReadData(data interface{}) error { 101 | return binary.Read(e.f, e.e.ByteOrder, data) 102 | } 103 | 104 | func (e *elfFile) WriteData(data interface{}) error { 105 | return binary.Write(e.f, e.e.ByteOrder, data) 106 | } 107 | 108 | func (e *elfFile) Write(b []byte) error { 109 | _, err := e.f.Write(b) 110 | return err 111 | } 112 | 113 | func (e *elfFile) WriteAt(b []byte, off uint64) error { 114 | _, err := e.f.WriteAt(b, int64(off)) 115 | return err 116 | } 117 | 118 | func (e *elfFile) Read8() (uint8, error) { 119 | _, err := e.f.Read(e.buffer[:1]) 120 | return e.buffer[0], err 121 | } 122 | 123 | func (e *elfFile) Read16() (uint16, error) { 124 | _, err := e.f.Read(e.buffer[:2]) 125 | return e.e.ByteOrder.Uint16(e.buffer[:]), err 126 | } 127 | 128 | func (e *elfFile) Read32() (uint32, error) { 129 | _, err := e.f.Read(e.buffer[:4]) 130 | return e.e.ByteOrder.Uint32(e.buffer[:]), err 131 | } 132 | 133 | func (e *elfFile) Read64() (uint64, error) { 134 | _, err := e.f.Read(e.buffer[:8]) 135 | return e.e.ByteOrder.Uint64(e.buffer[:]), err 136 | } 137 | 138 | func (e *elfFile) Read8At(where int64) (uint8, error) { 139 | _, err := e.f.ReadAt(e.buffer[:1], where) 140 | return e.buffer[0], err 141 | } 142 | 143 | func (e *elfFile) Read16At(where int64) (uint16, error) { 144 | _, err := e.f.ReadAt(e.buffer[:2], where) 145 | return e.e.ByteOrder.Uint16(e.buffer[:]), err 146 | } 147 | 148 | func (e *elfFile) Read32At(where int64) (uint32, error) { 149 | _, err := e.f.ReadAt(e.buffer[:4], where) 150 | return e.e.ByteOrder.Uint32(e.buffer[:]), err 151 | } 152 | 153 | func (e *elfFile) Read64At(where int64) (uint64, error) { 154 | _, err := e.f.ReadAt(e.buffer[:8], where) 155 | return e.e.ByteOrder.Uint64(e.buffer[:]), err 156 | } 157 | 158 | func (e *elfFile) Write8(data uint8) error { 159 | _, err := e.f.Write([]byte{data}) 160 | return err 161 | } 162 | 163 | func (e *elfFile) Write16(data uint16) error { 164 | e.e.ByteOrder.PutUint16(e.buffer[:], data) 165 | _, err := e.f.Write(e.buffer[:2]) 166 | return err 167 | } 168 | 169 | func (e *elfFile) Write32(data uint32) error { 170 | e.e.ByteOrder.PutUint32(e.buffer[:], data) 171 | _, err := e.f.Write(e.buffer[:4]) 172 | return err 173 | } 174 | 175 | func (e *elfFile) Write64(data uint64) error { 176 | e.e.ByteOrder.PutUint64(e.buffer[:], data) 177 | _, err := e.f.Write(e.buffer[:8]) 178 | return err 179 | } 180 | 181 | func (e *elfFile) Write8At(data uint8, where uint64) error { 182 | return e.WriteAt([]byte{data}, where) 183 | } 184 | 185 | func (e *elfFile) Write16At(data uint16, where uint64) error { 186 | e.e.ByteOrder.PutUint16(e.buffer[:], data) 187 | return e.WriteAt(e.buffer[:2], where) 188 | } 189 | 190 | func (e *elfFile) Write32At(data uint32, where uint64) error { 191 | e.e.ByteOrder.PutUint32(e.buffer[:], data) 192 | return e.WriteAt(e.buffer[:4], where) 193 | } 194 | 195 | func (e *elfFile) Write64At(data uint64, where uint64) error { 196 | e.e.ByteOrder.PutUint64(e.buffer[:], data) 197 | return e.WriteAt(e.buffer[:8], where) 198 | } 199 | 200 | func (e *elfFile) WriteSections() error { 201 | if _, err := e.f.Seek(int64(e.shoff), io.SeekStart); err != nil { 202 | return err 203 | } 204 | shstrtab := 0 205 | shstrtabpos := e.shoff 206 | names := make([]uint64, len(e.e.Sections)) 207 | for i, sec := range e.e.Sections { 208 | names[i] = e.shoff - shstrtabpos 209 | if err := e.Write(append([]byte(sec.Name), 0)); err != nil { 210 | return err 211 | } 212 | e.shoff += uint64(len(sec.Name)) + 1 213 | if sec.Name == ".shstrtab" { 214 | shstrtab = i 215 | } 216 | } 217 | e.e.Sections[shstrtab].Offset = shstrtabpos 218 | e.e.Sections[shstrtab].FileSize = e.shoff - shstrtabpos 219 | 220 | for i, sec := range e.e.Sections { 221 | if err := e.WriteSection(sec.SectionHeader, names[i]); err != nil { 222 | return err 223 | } 224 | } 225 | switch e.e.Class { 226 | case elf.ELFCLASS32: 227 | if err := e.Write32At(uint32(e.shoff), 0x20); err != nil { 228 | return err 229 | } 230 | if err := e.Write16At(uint16(len(e.e.Sections)), 0x30); err != nil { 231 | return err 232 | } 233 | case elf.ELFCLASS64: 234 | if err := e.Write64At(e.shoff, 0x28); err != nil { 235 | return err 236 | } 237 | if err := e.Write16At(uint16(len(e.e.Sections)), 0x3C); err != nil { 238 | return err 239 | } 240 | } 241 | return nil 242 | } 243 | 244 | func (e *elfFile) WriteSection(sh elf.SectionHeader, name uint64) error { 245 | switch e.e.Class { 246 | case elf.ELFCLASS32: 247 | hdr := elf.Section32{ 248 | Name: uint32(name), 249 | Type: uint32(sh.Type), 250 | Flags: uint32(sh.Flags), 251 | Addr: uint32(sh.Addr), 252 | Off: uint32(sh.Offset), 253 | Size: uint32(sh.FileSize), 254 | Link: sh.Link, 255 | Info: sh.Info, 256 | Addralign: uint32(sh.Addralign), 257 | Entsize: uint32(sh.Entsize), 258 | } 259 | return e.WriteData(hdr) 260 | case elf.ELFCLASS64: 261 | hdr := elf.Section64{ 262 | Name: uint32(name), 263 | Type: uint32(sh.Type), 264 | Flags: uint64(sh.Flags), 265 | Addr: sh.Addr, 266 | Off: sh.Offset, 267 | Size: sh.FileSize, 268 | Link: sh.Link, 269 | Info: sh.Info, 270 | Addralign: sh.Addralign, 271 | Entsize: sh.Entsize, 272 | } 273 | return e.WriteData(hdr) 274 | } 275 | // compression header not handeled 276 | return errors.New("Unknown elf bit size") 277 | } 278 | 279 | func (e *elfFile) WritePrograms() error { 280 | if _, err := e.Seek(int64(e.phoff), io.SeekStart); err != nil { 281 | return err 282 | } 283 | for _, prog := range e.e.Progs { 284 | if err := e.WriteProgram(prog.ProgHeader); err != nil { 285 | return err 286 | } 287 | } 288 | switch e.e.Class { 289 | case elf.ELFCLASS32: 290 | if err := e.Write32At(uint32(e.phoff), 0x1C); err != nil { 291 | return err 292 | } 293 | if err := e.Write16At(uint16(len(e.e.Progs)), 0x2C); err != nil { 294 | return err 295 | } 296 | case elf.ELFCLASS64: 297 | if err := e.Write64At(e.phoff, 0x20); err != nil { 298 | return err 299 | } 300 | if err := e.Write16At(uint16(len(e.e.Progs)), 0x38); err != nil { 301 | return err 302 | } 303 | } 304 | return nil 305 | } 306 | 307 | func (e *elfFile) WriteProgram(ph elf.ProgHeader) error { 308 | switch e.e.Class { 309 | case elf.ELFCLASS32: 310 | hdr := elf.Prog32{ 311 | Type: uint32(ph.Type), 312 | Flags: uint32(ph.Flags), 313 | Off: uint32(ph.Off), 314 | Vaddr: uint32(ph.Vaddr), 315 | Paddr: uint32(ph.Paddr), 316 | Filesz: uint32(ph.Filesz), 317 | Memsz: uint32(ph.Memsz), 318 | Align: uint32(ph.Align), 319 | } 320 | return e.WriteData(hdr) 321 | case elf.ELFCLASS64: 322 | hdr := elf.Prog64{ 323 | Type: uint32(ph.Type), 324 | Flags: uint32(ph.Flags), 325 | Off: ph.Off, 326 | Vaddr: ph.Vaddr, 327 | Paddr: ph.Paddr, 328 | Filesz: ph.Filesz, 329 | Memsz: ph.Memsz, 330 | Align: ph.Align, 331 | } 332 | return e.WriteData(hdr) 333 | } 334 | return errors.New("Unknown elf bit size") 335 | } 336 | 337 | func (e *elfFile) Read(b []byte) error { 338 | _, err := e.f.Read(b) 339 | return err 340 | } 341 | 342 | func (e *elfFile) Copy(src, dst int64, length int) error { 343 | buffer := make([]byte, length) 344 | _, err := e.Seek(src, io.SeekStart) 345 | if err != nil { 346 | return err 347 | } 348 | err = e.Read(buffer) 349 | if err != nil { 350 | return err 351 | } 352 | _, err = e.Seek(dst, io.SeekStart) 353 | if err != nil { 354 | return err 355 | } 356 | err = e.Write(buffer) 357 | if err != nil { 358 | return err 359 | } 360 | return nil 361 | } 362 | 363 | func (e *elfFile) Close() error { 364 | return e.f.Close() 365 | } 366 | 367 | // Dyn contains a single entry of the dynamic table 368 | type Dyn struct { 369 | Tag elf.DynTag 370 | Val uint64 371 | } 372 | 373 | func padding(addr, align uint64) uint64 { 374 | align1 := align - 1 375 | return (align - (addr & align1)) & align1 376 | } 377 | 378 | // DynSymbol represents a dynamic symbol 379 | type DynSymbol struct { 380 | Name string 381 | Value uint64 382 | Size uint64 383 | Bind elf.SymBind 384 | Type elf.SymType 385 | Vis elf.SymVis 386 | Section int 387 | } 388 | 389 | func (e *elfFile) makeDynsym(elements []DynSymbol) (dynsym, dynstr []byte) { 390 | sym := &bytes.Buffer{} 391 | str := &bytes.Buffer{} 392 | for _, elem := range elements { 393 | namei := str.Len() 394 | str.Write(append([]byte(elem.Name), 0)) 395 | switch e.e.Class { 396 | case elf.ELFCLASS32: 397 | binary.Write(sym, e.e.ByteOrder, elf.Sym32{ 398 | Name: uint32(namei), 399 | Value: uint32(elem.Value), 400 | Size: uint32(elem.Size), 401 | Info: byte(elem.Bind)<<4 | byte(elem.Type)&0x0f, 402 | Other: byte(elem.Vis) & 0x03, 403 | Shndx: uint16(elem.Section), 404 | }) 405 | case elf.ELFCLASS64: 406 | binary.Write(sym, e.e.ByteOrder, elf.Sym64{ 407 | Name: uint32(namei), 408 | Value: uint64(elem.Value), 409 | Size: uint64(elem.Size), 410 | Info: byte(elem.Bind)<<4 | byte(elem.Type)&0x0f, 411 | Other: byte(elem.Vis) & 0x03, 412 | Shndx: uint16(elem.Section), 413 | }) 414 | } 415 | } 416 | if str.Len() == 0 { 417 | str.WriteByte(0) 418 | } 419 | return sym.Bytes(), str.Bytes() 420 | } 421 | 422 | func (e *elfFile) makeDynsec(elements []Dyn) []byte { 423 | ret := &bytes.Buffer{} 424 | switch e.e.Class { 425 | case elf.ELFCLASS32: 426 | var secs []elf.Dyn32 427 | for _, sec := range elements { 428 | secs = append(secs, elf.Dyn32{ 429 | Tag: int32(sec.Tag), 430 | Val: uint32(sec.Val), 431 | }) 432 | } 433 | binary.Write(ret, e.e.ByteOrder, secs) 434 | case elf.ELFCLASS64: 435 | var secs []elf.Dyn64 436 | for _, sec := range elements { 437 | secs = append(secs, elf.Dyn64{ 438 | Tag: int64(sec.Tag), 439 | Val: uint64(sec.Val), 440 | }) 441 | } 442 | binary.Write(ret, e.e.ByteOrder, secs) 443 | } 444 | return ret.Bytes() 445 | } 446 | 447 | // RelSymbol represents a symbol in need of relocation 448 | type RelSymbol struct { 449 | Off uint64 450 | SymNo uint64 451 | } 452 | 453 | func (e *elfFile) makeDynRel(symbols []RelSymbol) ([]byte, bool, uint64) { 454 | ret := &bytes.Buffer{} 455 | var rela bool 456 | var relt uint64 457 | switch e.e.Machine { 458 | case elf.EM_386: 459 | rela = false 460 | relt = uint64(elf.R_386_JMP_SLOT) 461 | case elf.EM_X86_64: 462 | rela = true 463 | relt = uint64(elf.R_X86_64_JMP_SLOT) 464 | default: 465 | log.Panic("Unknown machine type ", e.e.Machine) 466 | } 467 | 468 | var relsz uint64 469 | 470 | switch e.e.Class { 471 | case elf.ELFCLASS32: 472 | if rela { 473 | for _, symbol := range symbols { 474 | binary.Write(ret, e.e.ByteOrder, elf.Rela32{ 475 | Off: uint32(symbol.Off), 476 | Info: uint32(symbol.SymNo<<8 | relt), 477 | }) 478 | } 479 | relsz = 12 480 | } else { 481 | for _, symbol := range symbols { 482 | 483 | binary.Write(ret, e.e.ByteOrder, elf.Rel32{ 484 | Off: uint32(symbol.Off), 485 | Info: uint32(symbol.SymNo<<8 | relt), 486 | }) 487 | } 488 | relsz = 8 489 | } 490 | case elf.ELFCLASS64: 491 | if rela { 492 | for _, symbol := range symbols { 493 | binary.Write(ret, e.e.ByteOrder, elf.Rela64{ 494 | Off: symbol.Off, 495 | Info: symbol.SymNo<<32 | relt, 496 | }) 497 | } 498 | relsz = 24 499 | } else { 500 | for _, symbol := range symbols { 501 | binary.Write(ret, e.e.ByteOrder, elf.Rel64{ 502 | Off: symbol.Off, 503 | Info: symbol.SymNo<<32 | relt, 504 | }) 505 | } 506 | relsz = 16 507 | } 508 | } 509 | return ret.Bytes(), rela, relsz 510 | } 511 | 512 | func main() { 513 | doCopy() 514 | f, err := openElfFile(out) 515 | if err != nil { 516 | log.Fatal(err) 517 | } 518 | 519 | /* 520 | KEEP EXEC (we are not dyn after all) 521 | try to put new program headers, dyn, interp into first 4k 522 | 0 -+-----------------------------------+-- 523 | | ELF | 524 | +-----------------------------------+ 525 | | program headers | 526 | +-----------------------------------+ 527 | | interp | 528 | +-----------------------------------+ 529 | | dyn stuff | 530 | +-----------------------------------+ 531 | | other stuff that needs relocation | 532 | +-----------------------------------+-- ensure mapping until here 533 | +-----------------------------------+ 534 | entry -> | Everything else (e.g., text) | 535 | +-----------------------------------+ 536 | | .shstrtab | 537 | +-----------------------------------+ 538 | | Section headers | 539 | +-----------------------------------+ 540 | */ 541 | 542 | // First some sanity checks - and checks if we can do our meddling, after all we don't support everything in this POC 543 | 544 | if f.e.Type != elf.ET_EXEC { 545 | log.Panic("only static binaries not using an interp supported") 546 | } 547 | 548 | var base uint64 549 | var baseProg int 550 | 551 | for i, prog := range f.e.Progs { 552 | if prog.Type == elf.PT_INTERP || prog.Type == elf.PT_DYNAMIC { 553 | log.Panic("only static binaries not using an interp supported") 554 | } 555 | if prog.Type == elf.PT_LOAD { 556 | if base == 0 { 557 | base = prog.Vaddr 558 | baseProg = i 559 | } else if prog.Vaddr < base { 560 | base = prog.Vaddr 561 | baseProg = i 562 | } 563 | } 564 | } 565 | 566 | if uint64(f.phoff+f.phentsize*uint64(len(f.e.Progs))) > f.e.Entry { 567 | log.Panic("Not enough space before entry point") 568 | } 569 | 570 | interpProg := len(f.e.Progs) 571 | 572 | f.e.Progs = append(f.e.Progs, &elf.Prog{ 573 | ProgHeader: elf.ProgHeader{ 574 | Type: elf.PT_INTERP, 575 | Flags: elf.PF_R, 576 | Off: 0, // fill later 577 | Vaddr: 0, // fill later 578 | Paddr: 0, // fill later 579 | Filesz: 0, // fill later 580 | Memsz: 0, // fill later 581 | Align: 1, 582 | }}) 583 | 584 | dynsecProg := len(f.e.Progs) 585 | 586 | f.e.Progs = append(f.e.Progs, &elf.Prog{ 587 | ProgHeader: elf.ProgHeader{ 588 | Type: elf.PT_DYNAMIC, 589 | Flags: elf.PF_R | elf.PF_W, 590 | Off: 0, // fill later 591 | Vaddr: 0, // fill later 592 | Paddr: 0, // fill later 593 | Filesz: 0, // fill later 594 | Memsz: 0, // fill later 595 | Align: 8, 596 | }}) 597 | 598 | interpPos := f.phoff + f.phentsize*uint64(len(f.e.Progs)) 599 | interpB := append([]byte(interp), 0) 600 | interpLen := uint64(len(interpB)) 601 | 602 | f.e.Progs[interpProg].Off = interpPos 603 | f.e.Progs[interpProg].Vaddr = interpPos + base 604 | f.e.Progs[interpProg].Paddr = interpPos + base 605 | f.e.Progs[interpProg].Filesz = interpLen 606 | f.e.Progs[interpProg].Memsz = interpLen 607 | 608 | hashPos := interpPos + interpLen 609 | hashPos += padding(hashPos, 8) 610 | hash := make([]byte, 8*4) // Empty 64bit DT_HASH 611 | hashLen := uint64(len(hash)) 612 | 613 | var relList []RelSymbol 614 | 615 | var symsection int 616 | 617 | symbolList, err := f.e.Symbols() 618 | if err != nil { 619 | log.Fatal(err) 620 | } 621 | 622 | var symdefs []DynSymbol 623 | 624 | symdefs = append(symdefs, DynSymbol{ 625 | Name: "", 626 | Value: 0, 627 | Size: 0, 628 | Bind: elf.STB_LOCAL, 629 | Type: elf.STT_NOTYPE, 630 | Vis: elf.STV_DEFAULT, 631 | Section: int(elf.SHN_UNDEF), 632 | }) 633 | 634 | for _, sym := range symbolList { 635 | if strings.HasSuffix(sym.Name, "__dynload") { 636 | parts := strings.Split(sym.Name, ".") 637 | name := parts[len(parts)-1] 638 | dynsym := name[:len(name)-9] 639 | 640 | symsection = int(sym.Section) 641 | relList = append(relList, RelSymbol{ 642 | Off: sym.Value, 643 | SymNo: uint64(len(symdefs)), 644 | }) 645 | symdefs = append(symdefs, DynSymbol{ 646 | Name: dynsym, 647 | Value: 0, 648 | Size: 0, 649 | Bind: elf.STB_GLOBAL, 650 | Type: elf.STT_FUNC, 651 | Vis: elf.STV_DEFAULT, 652 | Section: int(elf.SHN_UNDEF), 653 | }) 654 | } 655 | } 656 | 657 | dynsym, dynstr := f.makeDynsym(symdefs) 658 | 659 | var libOffsets []uint64 660 | 661 | for _, l := range libs { 662 | libOffsets = append(libOffsets, uint64(len(dynstr))) 663 | dynstr = append(dynstr, []byte(l)...) 664 | dynstr = append(dynstr, 0) 665 | } 666 | 667 | dynsymLocal := 0 668 | dynstrPos := hashPos + hashLen 669 | dynstrLen := uint64(len(dynstr)) 670 | 671 | dynsymPos := dynstrPos + dynstrLen 672 | dynsymPos += padding(dynsymPos, 8) 673 | dynsymLen := uint64(len(dynsym)) 674 | 675 | // TODO: DT_BIND_NOW? 676 | 677 | dynrel, rela, relsz := f.makeDynRel(relList) 678 | dynrelPos := dynsymPos + dynsymLen 679 | dynrelPos += padding(dynrelPos, 8) 680 | dynrelLen := uint64(len(dynrel)) 681 | 682 | var dynsecs []Dyn 683 | for _, offset := range libOffsets { 684 | dynsecs = append(dynsecs, Dyn{Tag: elf.DT_NEEDED, Val: uint64(offset)}) 685 | } 686 | 687 | if rela { 688 | dynsecs = append(dynsecs, Dyn{Tag: elf.DT_RELA, Val: uint64(base + dynrelPos)}) 689 | dynsecs = append(dynsecs, Dyn{Tag: elf.DT_RELASZ, Val: uint64(dynrelLen)}) 690 | dynsecs = append(dynsecs, Dyn{Tag: elf.DT_RELAENT, Val: uint64(relsz)}) 691 | } else { 692 | dynsecs = append(dynsecs, Dyn{Tag: elf.DT_REL, Val: uint64(base + dynrelPos)}) 693 | dynsecs = append(dynsecs, Dyn{Tag: elf.DT_RELSZ, Val: uint64(dynrelLen)}) 694 | dynsecs = append(dynsecs, Dyn{Tag: elf.DT_RELENT, Val: uint64(relsz)}) 695 | } 696 | 697 | dynsecs = append(dynsecs, []Dyn{ 698 | {Tag: elf.DT_STRTAB, Val: base + dynstrPos}, 699 | {Tag: elf.DT_STRSZ, Val: dynstrLen}, 700 | {Tag: elf.DT_SYMTAB, Val: base + dynsymPos}, 701 | {Tag: elf.DT_SYMENT, Val: dynsymLen}, 702 | {Tag: elf.DT_HASH, Val: hashPos + base}, 703 | {Tag: elf.DT_NULL, Val: 0}, 704 | }...) 705 | 706 | dynsec := f.makeDynsec(dynsecs) 707 | dynsecPos := dynrelPos + dynrelLen 708 | dynsecPos += padding(dynsecPos, 8) 709 | dynsecLen := uint64(len(dynsec)) 710 | 711 | f.e.Progs[dynsecProg].Off = dynsecPos 712 | f.e.Progs[dynsecProg].Vaddr = dynsecPos + base 713 | f.e.Progs[dynsecProg].Paddr = dynsecPos + base 714 | f.e.Progs[dynsecProg].Filesz = dynsecLen 715 | f.e.Progs[dynsecProg].Memsz = dynsecLen 716 | 717 | afterDynsec := dynsecPos + dynsecLen 718 | 719 | relPos := afterDynsec 720 | var torelocate []*elf.Section 721 | relocated := make(map[int]bool) 722 | 723 | for { 724 | var newRelocate []*elf.Section 725 | for i, sec := range f.e.Sections { 726 | if sec.Type == elf.SHT_NULL { 727 | continue 728 | } 729 | if sec.Offset < relPos && !relocated[i] { 730 | newRelocate = append(newRelocate, sec) 731 | relocated[i] = true 732 | } 733 | } 734 | if len(newRelocate) == 0 { 735 | break 736 | } 737 | torelocate = append(torelocate, newRelocate...) 738 | 739 | sort.Slice(torelocate, func(i, j int) bool { return torelocate[i].Offset < torelocate[j].Offset }) 740 | relPos = afterDynsec 741 | for _, sec := range torelocate { 742 | relPos += sec.Size 743 | if sec.Addralign > 1 { 744 | relPos += padding(relPos, sec.Addralign) 745 | } 746 | } 747 | } 748 | 749 | for _, sec := range torelocate { 750 | data := make([]byte, sec.Size) 751 | if _, err := f.f.ReadAt(data, int64(sec.Offset)); err != nil { 752 | log.Fatal(err) 753 | } 754 | if sec.Addralign > 1 { 755 | afterDynsec += padding(afterDynsec, sec.Addralign) 756 | } 757 | if err := f.WriteAt(data, afterDynsec); err != nil { 758 | log.Fatal(err) 759 | } 760 | for _, prog := range f.e.Progs { 761 | if prog.Off == sec.Offset { 762 | prog.Off = afterDynsec 763 | } 764 | if prog.Vaddr == sec.Offset+base { 765 | prog.Vaddr = afterDynsec + base 766 | prog.Paddr = afterDynsec + base 767 | } 768 | } 769 | 770 | sec.Addr += afterDynsec - sec.Offset // or base + offset 771 | sec.Offset, afterDynsec = afterDynsec, afterDynsec+sec.Offset 772 | } 773 | 774 | if afterDynsec > f.e.Entry { 775 | log.Fatal("not enough space before entry point") 776 | } 777 | 778 | if f.e.Progs[baseProg].Filesz < afterDynsec { 779 | f.e.Progs[baseProg].Filesz = afterDynsec 780 | f.e.Progs[baseProg].Memsz = afterDynsec 781 | } 782 | 783 | if err := f.WritePrograms(); err != nil { 784 | log.Fatal(err) 785 | } 786 | 787 | if err := f.WriteAt(interpB, interpPos); err != nil { 788 | log.Fatal(err) 789 | } 790 | 791 | if err := f.WriteAt(hash, hashPos); err != nil { 792 | log.Fatal(err) 793 | } 794 | 795 | if err := f.WriteAt(dynstr, dynstrPos); err != nil { 796 | log.Fatal(err) 797 | } 798 | 799 | if err := f.WriteAt(dynsym, dynsymPos); err != nil { 800 | log.Fatal(err) 801 | } 802 | 803 | if err := f.WriteAt(dynrel, dynrelPos); err != nil { 804 | log.Fatal(err) 805 | } 806 | 807 | if err := f.WriteAt(dynsec, dynsecPos); err != nil { 808 | log.Fatal(err) 809 | } 810 | 811 | f.e.Sections = append(f.e.Sections, &elf.Section{ 812 | SectionHeader: elf.SectionHeader{ 813 | Name: ".interp", 814 | Type: elf.SHT_PROGBITS, 815 | Flags: elf.SHF_ALLOC, 816 | Addr: base + interpPos, 817 | Offset: interpPos, 818 | FileSize: interpLen, 819 | Addralign: 1, 820 | }}) 821 | 822 | dynstrI := len(f.e.Sections) 823 | 824 | f.e.Sections = append(f.e.Sections, &elf.Section{ 825 | SectionHeader: elf.SectionHeader{ 826 | Name: ".dynstr", 827 | Type: elf.SHT_STRTAB, 828 | Flags: elf.SHF_ALLOC, 829 | Addr: base + dynstrPos, 830 | Offset: dynstrPos, 831 | FileSize: dynstrLen, 832 | Addralign: 1, 833 | }}) 834 | 835 | entSize := uint64(24) 836 | if f.e.Class == elf.ELFCLASS32 { 837 | entSize = 16 838 | } 839 | 840 | dynsymSec := len(f.e.Sections) 841 | 842 | f.e.Sections = append(f.e.Sections, &elf.Section{ 843 | SectionHeader: elf.SectionHeader{ 844 | Name: ".dynsym", 845 | Type: elf.SHT_DYNSYM, 846 | Flags: elf.SHF_ALLOC, 847 | Addr: base + dynsymPos, 848 | Offset: dynsymPos, 849 | FileSize: dynsymLen, 850 | Addralign: 8, 851 | Link: uint32(dynstrI), 852 | Entsize: entSize, 853 | Info: uint32(dynsymLocal + 1), 854 | }}) 855 | 856 | entSize = uint64(16) 857 | if f.e.Class == elf.ELFCLASS32 { 858 | entSize = 8 859 | } 860 | 861 | f.e.Sections = append(f.e.Sections, &elf.Section{ 862 | SectionHeader: elf.SectionHeader{ 863 | Name: ".dynamic", 864 | Type: elf.SHT_DYNAMIC, 865 | Flags: elf.SHF_ALLOC | elf.SHF_WRITE, 866 | Addr: base + dynsecPos, 867 | Offset: dynsecPos, 868 | FileSize: dynsecLen, 869 | Addralign: 8, 870 | Link: uint32(dynstrI), 871 | Entsize: entSize, 872 | }}) 873 | 874 | dynname := ".rel" 875 | if rela { 876 | dynname = ".rela" 877 | } 878 | dynname += f.e.Sections[symsection].Name 879 | 880 | shtype := elf.SHT_REL 881 | if rela { 882 | shtype = elf.SHT_RELA 883 | } 884 | 885 | f.e.Sections = append(f.e.Sections, &elf.Section{ 886 | SectionHeader: elf.SectionHeader{ 887 | Name: dynname, 888 | Type: shtype, 889 | Flags: elf.SHF_ALLOC, 890 | Addr: base + dynrelPos, 891 | Offset: dynrelPos, 892 | FileSize: dynrelLen, 893 | Addralign: 8, 894 | Link: uint32(dynsymSec), 895 | Info: uint32(symsection), 896 | Entsize: relsz, 897 | }}) 898 | 899 | f.e.Sections = append(f.e.Sections, &elf.Section{ 900 | SectionHeader: elf.SectionHeader{ 901 | Name: ".hash", 902 | Type: elf.SHT_HASH, 903 | Flags: elf.SHF_ALLOC, 904 | Addr: base + hashPos, 905 | Offset: hashPos, 906 | FileSize: hashLen, 907 | Addralign: 8, 908 | Link: uint32(dynsymSec), 909 | }}) 910 | 911 | shoff, err := f.f.Seek(0, io.SeekEnd) 912 | if err != nil { 913 | log.Fatal(err) 914 | } 915 | f.shoff = uint64(shoff) 916 | 917 | if err := f.WriteSections(); err != nil { 918 | log.Fatal(err) 919 | } 920 | 921 | f.Close() 922 | } 923 | -------------------------------------------------------------------------------- /steps/3_goffi/rewrite.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "debug/elf" 6 | "encoding/binary" 7 | "errors" 8 | "io" 9 | "log" 10 | "os" 11 | "sort" 12 | "strings" 13 | ) 14 | 15 | const ( 16 | in = "main" 17 | out = "dyn" 18 | interp = "/lib64/ld-linux-x86-64.so.2" 19 | ) 20 | 21 | var libs = []string{"libc.so.6", "libpthread.so.0", "libcalltest.so.1"} 22 | 23 | func doCopy() { 24 | src, err := os.Open(in) 25 | if err != nil { 26 | log.Fatal("couldn't open", in, ":", err) 27 | } 28 | defer src.Close() 29 | dst, err := os.OpenFile(out, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0777) 30 | if err != nil { 31 | log.Fatal("couldn't create", out, ":", err) 32 | } 33 | defer dst.Close() 34 | _, err = io.Copy(dst, src) 35 | if err != nil { 36 | log.Fatal("couldn't copy:", err) 37 | } 38 | } 39 | 40 | type elfFile struct { 41 | f *os.File 42 | e *elf.File 43 | phoff uint64 44 | phentsize uint64 45 | shoff uint64 46 | shentsize uint64 47 | shstrndx uint64 48 | buffer [1024]byte 49 | } 50 | 51 | func openElfFile(name string) (*elfFile, error) { 52 | ret := &elfFile{} 53 | var err error 54 | ret.f, err = os.OpenFile(name, os.O_RDWR, 0777) 55 | if err != nil { 56 | return nil, err 57 | } 58 | ret.e, err = elf.NewFile(ret.f) 59 | if err != nil { 60 | return nil, err 61 | } 62 | 63 | _, err = ret.Seek(0, io.SeekCurrent) 64 | if err != nil { 65 | return nil, err 66 | } 67 | 68 | switch ret.e.Class { 69 | case elf.ELFCLASS32: 70 | var hdr elf.Header32 71 | err = ret.ReadData(&hdr) 72 | if err != nil { 73 | return nil, err 74 | } 75 | ret.phoff = uint64(hdr.Phoff) 76 | ret.phentsize = uint64(hdr.Phentsize) 77 | ret.shoff = uint64(hdr.Shoff) 78 | ret.shentsize = uint64(hdr.Shentsize) 79 | ret.shstrndx = uint64(hdr.Shstrndx) 80 | case elf.ELFCLASS64: 81 | var hdr elf.Header64 82 | err = ret.ReadData(&hdr) 83 | if err != nil { 84 | return nil, err 85 | } 86 | ret.phoff = hdr.Phoff 87 | ret.phentsize = uint64(hdr.Phentsize) 88 | ret.shoff = hdr.Shoff 89 | ret.shentsize = uint64(hdr.Shentsize) 90 | ret.shstrndx = uint64(hdr.Shstrndx) 91 | } 92 | 93 | return ret, nil 94 | } 95 | 96 | func (e *elfFile) Seek(where int64, whence int) (int64, error) { 97 | return e.f.Seek(where, whence) 98 | } 99 | 100 | func (e *elfFile) ReadData(data interface{}) error { 101 | return binary.Read(e.f, e.e.ByteOrder, data) 102 | } 103 | 104 | func (e *elfFile) WriteData(data interface{}) error { 105 | return binary.Write(e.f, e.e.ByteOrder, data) 106 | } 107 | 108 | func (e *elfFile) Write(b []byte) error { 109 | _, err := e.f.Write(b) 110 | return err 111 | } 112 | 113 | func (e *elfFile) WriteAt(b []byte, off uint64) error { 114 | _, err := e.f.WriteAt(b, int64(off)) 115 | return err 116 | } 117 | 118 | func (e *elfFile) Read8() (uint8, error) { 119 | _, err := e.f.Read(e.buffer[:1]) 120 | return e.buffer[0], err 121 | } 122 | 123 | func (e *elfFile) Read16() (uint16, error) { 124 | _, err := e.f.Read(e.buffer[:2]) 125 | return e.e.ByteOrder.Uint16(e.buffer[:]), err 126 | } 127 | 128 | func (e *elfFile) Read32() (uint32, error) { 129 | _, err := e.f.Read(e.buffer[:4]) 130 | return e.e.ByteOrder.Uint32(e.buffer[:]), err 131 | } 132 | 133 | func (e *elfFile) Read64() (uint64, error) { 134 | _, err := e.f.Read(e.buffer[:8]) 135 | return e.e.ByteOrder.Uint64(e.buffer[:]), err 136 | } 137 | 138 | func (e *elfFile) Read8At(where int64) (uint8, error) { 139 | _, err := e.f.ReadAt(e.buffer[:1], where) 140 | return e.buffer[0], err 141 | } 142 | 143 | func (e *elfFile) Read16At(where int64) (uint16, error) { 144 | _, err := e.f.ReadAt(e.buffer[:2], where) 145 | return e.e.ByteOrder.Uint16(e.buffer[:]), err 146 | } 147 | 148 | func (e *elfFile) Read32At(where int64) (uint32, error) { 149 | _, err := e.f.ReadAt(e.buffer[:4], where) 150 | return e.e.ByteOrder.Uint32(e.buffer[:]), err 151 | } 152 | 153 | func (e *elfFile) Read64At(where int64) (uint64, error) { 154 | _, err := e.f.ReadAt(e.buffer[:8], where) 155 | return e.e.ByteOrder.Uint64(e.buffer[:]), err 156 | } 157 | 158 | func (e *elfFile) Write8(data uint8) error { 159 | _, err := e.f.Write([]byte{data}) 160 | return err 161 | } 162 | 163 | func (e *elfFile) Write16(data uint16) error { 164 | e.e.ByteOrder.PutUint16(e.buffer[:], data) 165 | _, err := e.f.Write(e.buffer[:2]) 166 | return err 167 | } 168 | 169 | func (e *elfFile) Write32(data uint32) error { 170 | e.e.ByteOrder.PutUint32(e.buffer[:], data) 171 | _, err := e.f.Write(e.buffer[:4]) 172 | return err 173 | } 174 | 175 | func (e *elfFile) Write64(data uint64) error { 176 | e.e.ByteOrder.PutUint64(e.buffer[:], data) 177 | _, err := e.f.Write(e.buffer[:8]) 178 | return err 179 | } 180 | 181 | func (e *elfFile) Write8At(data uint8, where uint64) error { 182 | return e.WriteAt([]byte{data}, where) 183 | } 184 | 185 | func (e *elfFile) Write16At(data uint16, where uint64) error { 186 | e.e.ByteOrder.PutUint16(e.buffer[:], data) 187 | return e.WriteAt(e.buffer[:2], where) 188 | } 189 | 190 | func (e *elfFile) Write32At(data uint32, where uint64) error { 191 | e.e.ByteOrder.PutUint32(e.buffer[:], data) 192 | return e.WriteAt(e.buffer[:4], where) 193 | } 194 | 195 | func (e *elfFile) Write64At(data uint64, where uint64) error { 196 | e.e.ByteOrder.PutUint64(e.buffer[:], data) 197 | return e.WriteAt(e.buffer[:8], where) 198 | } 199 | 200 | func (e *elfFile) WriteSections() error { 201 | if _, err := e.f.Seek(int64(e.shoff), io.SeekStart); err != nil { 202 | return err 203 | } 204 | shstrtab := 0 205 | shstrtabpos := e.shoff 206 | names := make([]uint64, len(e.e.Sections)) 207 | for i, sec := range e.e.Sections { 208 | names[i] = e.shoff - shstrtabpos 209 | if err := e.Write(append([]byte(sec.Name), 0)); err != nil { 210 | return err 211 | } 212 | e.shoff += uint64(len(sec.Name)) + 1 213 | if sec.Name == ".shstrtab" { 214 | shstrtab = i 215 | } 216 | } 217 | e.e.Sections[shstrtab].Offset = shstrtabpos 218 | e.e.Sections[shstrtab].FileSize = e.shoff - shstrtabpos 219 | 220 | for i, sec := range e.e.Sections { 221 | if err := e.WriteSection(sec.SectionHeader, names[i]); err != nil { 222 | return err 223 | } 224 | } 225 | switch e.e.Class { 226 | case elf.ELFCLASS32: 227 | if err := e.Write32At(uint32(e.shoff), 0x20); err != nil { 228 | return err 229 | } 230 | if err := e.Write16At(uint16(len(e.e.Sections)), 0x30); err != nil { 231 | return err 232 | } 233 | case elf.ELFCLASS64: 234 | if err := e.Write64At(e.shoff, 0x28); err != nil { 235 | return err 236 | } 237 | if err := e.Write16At(uint16(len(e.e.Sections)), 0x3C); err != nil { 238 | return err 239 | } 240 | } 241 | return nil 242 | } 243 | 244 | func (e *elfFile) WriteSection(sh elf.SectionHeader, name uint64) error { 245 | switch e.e.Class { 246 | case elf.ELFCLASS32: 247 | hdr := elf.Section32{ 248 | Name: uint32(name), 249 | Type: uint32(sh.Type), 250 | Flags: uint32(sh.Flags), 251 | Addr: uint32(sh.Addr), 252 | Off: uint32(sh.Offset), 253 | Size: uint32(sh.FileSize), 254 | Link: sh.Link, 255 | Info: sh.Info, 256 | Addralign: uint32(sh.Addralign), 257 | Entsize: uint32(sh.Entsize), 258 | } 259 | return e.WriteData(hdr) 260 | case elf.ELFCLASS64: 261 | hdr := elf.Section64{ 262 | Name: uint32(name), 263 | Type: uint32(sh.Type), 264 | Flags: uint64(sh.Flags), 265 | Addr: sh.Addr, 266 | Off: sh.Offset, 267 | Size: sh.FileSize, 268 | Link: sh.Link, 269 | Info: sh.Info, 270 | Addralign: sh.Addralign, 271 | Entsize: sh.Entsize, 272 | } 273 | return e.WriteData(hdr) 274 | } 275 | // compression header not handeled 276 | return errors.New("Unknown elf bit size") 277 | } 278 | 279 | func (e *elfFile) WritePrograms() error { 280 | if _, err := e.Seek(int64(e.phoff), io.SeekStart); err != nil { 281 | return err 282 | } 283 | for _, prog := range e.e.Progs { 284 | if err := e.WriteProgram(prog.ProgHeader); err != nil { 285 | return err 286 | } 287 | } 288 | switch e.e.Class { 289 | case elf.ELFCLASS32: 290 | if err := e.Write32At(uint32(e.phoff), 0x1C); err != nil { 291 | return err 292 | } 293 | if err := e.Write16At(uint16(len(e.e.Progs)), 0x2C); err != nil { 294 | return err 295 | } 296 | case elf.ELFCLASS64: 297 | if err := e.Write64At(e.phoff, 0x20); err != nil { 298 | return err 299 | } 300 | if err := e.Write16At(uint16(len(e.e.Progs)), 0x38); err != nil { 301 | return err 302 | } 303 | } 304 | return nil 305 | } 306 | 307 | func (e *elfFile) WriteProgram(ph elf.ProgHeader) error { 308 | switch e.e.Class { 309 | case elf.ELFCLASS32: 310 | hdr := elf.Prog32{ 311 | Type: uint32(ph.Type), 312 | Flags: uint32(ph.Flags), 313 | Off: uint32(ph.Off), 314 | Vaddr: uint32(ph.Vaddr), 315 | Paddr: uint32(ph.Paddr), 316 | Filesz: uint32(ph.Filesz), 317 | Memsz: uint32(ph.Memsz), 318 | Align: uint32(ph.Align), 319 | } 320 | return e.WriteData(hdr) 321 | case elf.ELFCLASS64: 322 | hdr := elf.Prog64{ 323 | Type: uint32(ph.Type), 324 | Flags: uint32(ph.Flags), 325 | Off: ph.Off, 326 | Vaddr: ph.Vaddr, 327 | Paddr: ph.Paddr, 328 | Filesz: ph.Filesz, 329 | Memsz: ph.Memsz, 330 | Align: ph.Align, 331 | } 332 | return e.WriteData(hdr) 333 | } 334 | return errors.New("Unknown elf bit size") 335 | } 336 | 337 | func (e *elfFile) Read(b []byte) error { 338 | _, err := e.f.Read(b) 339 | return err 340 | } 341 | 342 | func (e *elfFile) Copy(src, dst int64, length int) error { 343 | buffer := make([]byte, length) 344 | _, err := e.Seek(src, io.SeekStart) 345 | if err != nil { 346 | return err 347 | } 348 | err = e.Read(buffer) 349 | if err != nil { 350 | return err 351 | } 352 | _, err = e.Seek(dst, io.SeekStart) 353 | if err != nil { 354 | return err 355 | } 356 | err = e.Write(buffer) 357 | if err != nil { 358 | return err 359 | } 360 | return nil 361 | } 362 | 363 | func (e *elfFile) Close() error { 364 | return e.f.Close() 365 | } 366 | 367 | // Dyn contains a single entry of the dynamic table 368 | type Dyn struct { 369 | Tag elf.DynTag 370 | Val uint64 371 | } 372 | 373 | func padding(addr, align uint64) uint64 { 374 | align1 := align - 1 375 | return (align - (addr & align1)) & align1 376 | } 377 | 378 | // DynSymbol represents a dynamic symbol 379 | type DynSymbol struct { 380 | Name string 381 | Value uint64 382 | Size uint64 383 | Bind elf.SymBind 384 | Type elf.SymType 385 | Vis elf.SymVis 386 | Section int 387 | } 388 | 389 | func (e *elfFile) makeDynsym(elements []DynSymbol) (dynsym, dynstr []byte) { 390 | sym := &bytes.Buffer{} 391 | str := &bytes.Buffer{} 392 | for _, elem := range elements { 393 | namei := str.Len() 394 | str.Write(append([]byte(elem.Name), 0)) 395 | switch e.e.Class { 396 | case elf.ELFCLASS32: 397 | binary.Write(sym, e.e.ByteOrder, elf.Sym32{ 398 | Name: uint32(namei), 399 | Value: uint32(elem.Value), 400 | Size: uint32(elem.Size), 401 | Info: byte(elem.Bind)<<4 | byte(elem.Type)&0x0f, 402 | Other: byte(elem.Vis) & 0x03, 403 | Shndx: uint16(elem.Section), 404 | }) 405 | case elf.ELFCLASS64: 406 | binary.Write(sym, e.e.ByteOrder, elf.Sym64{ 407 | Name: uint32(namei), 408 | Value: uint64(elem.Value), 409 | Size: uint64(elem.Size), 410 | Info: byte(elem.Bind)<<4 | byte(elem.Type)&0x0f, 411 | Other: byte(elem.Vis) & 0x03, 412 | Shndx: uint16(elem.Section), 413 | }) 414 | } 415 | } 416 | if str.Len() == 0 { 417 | str.WriteByte(0) 418 | } 419 | return sym.Bytes(), str.Bytes() 420 | } 421 | 422 | func (e *elfFile) makeDynsec(elements []Dyn) []byte { 423 | ret := &bytes.Buffer{} 424 | switch e.e.Class { 425 | case elf.ELFCLASS32: 426 | var secs []elf.Dyn32 427 | for _, sec := range elements { 428 | secs = append(secs, elf.Dyn32{ 429 | Tag: int32(sec.Tag), 430 | Val: uint32(sec.Val), 431 | }) 432 | } 433 | binary.Write(ret, e.e.ByteOrder, secs) 434 | case elf.ELFCLASS64: 435 | var secs []elf.Dyn64 436 | for _, sec := range elements { 437 | secs = append(secs, elf.Dyn64{ 438 | Tag: int64(sec.Tag), 439 | Val: uint64(sec.Val), 440 | }) 441 | } 442 | binary.Write(ret, e.e.ByteOrder, secs) 443 | } 444 | return ret.Bytes() 445 | } 446 | 447 | // RelSymbol represents a symbol in need of relocation 448 | type RelSymbol struct { 449 | Off uint64 450 | SymNo uint64 451 | } 452 | 453 | func (e *elfFile) makeDynRel(symbols []RelSymbol) ([]byte, bool, uint64) { 454 | ret := &bytes.Buffer{} 455 | var rela bool 456 | var relt uint64 457 | switch e.e.Machine { 458 | case elf.EM_386: 459 | rela = false 460 | relt = uint64(elf.R_386_JMP_SLOT) 461 | case elf.EM_X86_64: 462 | rela = true 463 | relt = uint64(elf.R_X86_64_JMP_SLOT) 464 | default: 465 | log.Fatal("Unknown machine type ", e.e.Machine) 466 | } 467 | 468 | var relsz uint64 469 | 470 | switch e.e.Class { 471 | case elf.ELFCLASS32: 472 | if rela { 473 | for _, symbol := range symbols { 474 | binary.Write(ret, e.e.ByteOrder, elf.Rela32{ 475 | Off: uint32(symbol.Off), 476 | Info: uint32(symbol.SymNo<<8 | relt), 477 | }) 478 | } 479 | relsz = 12 480 | } else { 481 | for _, symbol := range symbols { 482 | 483 | binary.Write(ret, e.e.ByteOrder, elf.Rel32{ 484 | Off: uint32(symbol.Off), 485 | Info: uint32(symbol.SymNo<<8 | relt), 486 | }) 487 | } 488 | relsz = 8 489 | } 490 | case elf.ELFCLASS64: 491 | if rela { 492 | for _, symbol := range symbols { 493 | binary.Write(ret, e.e.ByteOrder, elf.Rela64{ 494 | Off: symbol.Off, 495 | Info: symbol.SymNo<<32 | relt, 496 | }) 497 | } 498 | relsz = 24 499 | } else { 500 | for _, symbol := range symbols { 501 | binary.Write(ret, e.e.ByteOrder, elf.Rel64{ 502 | Off: symbol.Off, 503 | Info: symbol.SymNo<<32 | relt, 504 | }) 505 | } 506 | relsz = 16 507 | } 508 | } 509 | return ret.Bytes(), rela, relsz 510 | } 511 | 512 | func main() { 513 | doCopy() 514 | f, err := openElfFile(out) 515 | if err != nil { 516 | log.Fatal(err) 517 | } 518 | 519 | /* 520 | KEEP EXEC (we are not dyn after all) 521 | try to put new program headers, dyn, interp into first 4k 522 | 0 -+-----------------------------------+-- 523 | | ELF | 524 | +-----------------------------------+ 525 | | program headers | 526 | +-----------------------------------+ 527 | | interp | 528 | +-----------------------------------+ 529 | should be | dyn stuff | 530 | below 4k ->+-----------------------------------+ 531 | | other stuff that needs relocation | 532 | +-----------------------------------+<- ensure mapping until here 533 | +-----------------------------------+ 534 | entry -> | Everything else (e.g., text) | 535 | +-----------------------------------+ 536 | | .shstrtab | 537 | +-----------------------------------+ 538 | | Section headers | 539 | +-----------------------------------+ 540 | */ 541 | 542 | // First some sanity checks - and checks if we can do our meddling, after all we don't support everything in this POC 543 | 544 | if f.e.Type != elf.ET_EXEC { 545 | log.Fatal("only static binaries not using an interp supported") 546 | } 547 | 548 | var base uint64 549 | var baseProg int 550 | 551 | for i, prog := range f.e.Progs { 552 | if prog.Type == elf.PT_INTERP || prog.Type == elf.PT_DYNAMIC { 553 | log.Fatal("only static binaries not using an interp supported") 554 | } 555 | if prog.Type == elf.PT_LOAD { 556 | if base == 0 { 557 | base = prog.Vaddr 558 | baseProg = i 559 | } else if prog.Vaddr < base { 560 | base = prog.Vaddr 561 | baseProg = i 562 | } 563 | } 564 | } 565 | 566 | if uint64(f.phoff+f.phentsize*uint64(len(f.e.Progs))) > f.e.Entry { 567 | log.Fatal("Not enough space before entry point") 568 | } 569 | 570 | interpProg := len(f.e.Progs) 571 | 572 | f.e.Progs = append(f.e.Progs, &elf.Prog{ 573 | ProgHeader: elf.ProgHeader{ 574 | Type: elf.PT_INTERP, 575 | Flags: elf.PF_R, 576 | Off: 0, // fill later 577 | Vaddr: 0, // fill later 578 | Paddr: 0, // fill later 579 | Filesz: 0, // fill later 580 | Memsz: 0, // fill later 581 | Align: 1, 582 | }}) 583 | 584 | dynsecProg := len(f.e.Progs) 585 | 586 | f.e.Progs = append(f.e.Progs, &elf.Prog{ 587 | ProgHeader: elf.ProgHeader{ 588 | Type: elf.PT_DYNAMIC, 589 | Flags: elf.PF_R | elf.PF_W, 590 | Off: 0, // fill later 591 | Vaddr: 0, // fill later 592 | Paddr: 0, // fill later 593 | Filesz: 0, // fill later 594 | Memsz: 0, // fill later 595 | Align: 8, 596 | }}) 597 | 598 | interpPos := f.phoff + f.phentsize*uint64(len(f.e.Progs)) 599 | interpB := append([]byte(interp), 0) 600 | interpLen := uint64(len(interpB)) 601 | 602 | f.e.Progs[interpProg].Off = interpPos 603 | f.e.Progs[interpProg].Vaddr = interpPos + base 604 | f.e.Progs[interpProg].Paddr = interpPos + base 605 | f.e.Progs[interpProg].Filesz = interpLen 606 | f.e.Progs[interpProg].Memsz = interpLen 607 | 608 | hashPos := interpPos + interpLen 609 | hashPos += padding(hashPos, 8) 610 | hash := make([]byte, 8*4) // Empty 64bit DT_HASH 611 | hashLen := uint64(len(hash)) 612 | 613 | var relList []RelSymbol 614 | 615 | var symsection int 616 | 617 | symbolList, err := f.e.Symbols() 618 | if err != nil { 619 | log.Fatal(err) 620 | } 621 | 622 | var symdefs []DynSymbol 623 | 624 | symdefs = append(symdefs, DynSymbol{ 625 | Name: "", 626 | Value: 0, 627 | Size: 0, 628 | Bind: elf.STB_LOCAL, 629 | Type: elf.STT_NOTYPE, 630 | Vis: elf.STV_DEFAULT, 631 | Section: int(elf.SHN_UNDEF), 632 | }) 633 | 634 | x_cgo_init := uint64(0) 635 | _cgo_init := uint64(0) 636 | _cgo_size := uint64(0) 637 | 638 | for _, sym := range symbolList { 639 | if strings.HasSuffix(sym.Name, "__dynload") { 640 | parts := strings.Split(sym.Name, ".") 641 | name := parts[len(parts)-1] 642 | dynsym := name[:len(name)-9] 643 | 644 | symsection = int(sym.Section) 645 | relList = append(relList, RelSymbol{ 646 | Off: sym.Value, 647 | SymNo: uint64(len(symdefs)), 648 | }) 649 | symdefs = append(symdefs, DynSymbol{ 650 | Name: dynsym, 651 | Value: 0, 652 | Size: 0, 653 | Bind: elf.STB_GLOBAL, 654 | Type: elf.STT_FUNC, 655 | Vis: elf.STV_DEFAULT, 656 | Section: int(elf.SHN_UNDEF), 657 | }) 658 | } 659 | if sym.Name == "x_cgo_init" { 660 | x_cgo_init = sym.Value 661 | } 662 | if sym.Name == "_cgo_init" { 663 | sec := f.e.Sections[sym.Section] 664 | _cgo_init = sym.Value - sec.Addr + sec.Offset 665 | _cgo_size = sym.Size 666 | } 667 | } 668 | 669 | if x_cgo_init != 0 && _cgo_init != 0 && _cgo_size != 0 { 670 | switch _cgo_size { 671 | case 4: 672 | f.Write32At(uint32(x_cgo_init), _cgo_init) 673 | case 8: 674 | f.Write64At(x_cgo_init, _cgo_init) 675 | default: 676 | log.Fatalln("Unknown symbol size", _cgo_size) 677 | } 678 | } 679 | 680 | dynsym, dynstr := f.makeDynsym(symdefs) 681 | 682 | var libOffsets []uint64 683 | 684 | for _, l := range libs { 685 | libOffsets = append(libOffsets, uint64(len(dynstr))) 686 | dynstr = append(dynstr, []byte(l)...) 687 | dynstr = append(dynstr, 0) 688 | } 689 | 690 | dynsymLocal := 0 691 | dynstrPos := hashPos + hashLen 692 | dynstrLen := uint64(len(dynstr)) 693 | 694 | dynsymPos := dynstrPos + dynstrLen 695 | dynsymPos += padding(dynsymPos, 8) 696 | dynsymLen := uint64(len(dynsym)) 697 | 698 | // TODO: DT_BIND_NOW? 699 | 700 | dynrel, rela, relsz := f.makeDynRel(relList) 701 | dynrelPos := dynsymPos + dynsymLen 702 | dynrelPos += padding(dynrelPos, 8) 703 | dynrelLen := uint64(len(dynrel)) 704 | 705 | var dynsecs []Dyn 706 | for _, offset := range libOffsets { 707 | dynsecs = append(dynsecs, Dyn{Tag: elf.DT_NEEDED, Val: uint64(offset)}) 708 | } 709 | 710 | if rela { 711 | dynsecs = append(dynsecs, Dyn{Tag: elf.DT_RELA, Val: uint64(base + dynrelPos)}) 712 | dynsecs = append(dynsecs, Dyn{Tag: elf.DT_RELASZ, Val: uint64(dynrelLen)}) 713 | dynsecs = append(dynsecs, Dyn{Tag: elf.DT_RELAENT, Val: uint64(relsz)}) 714 | } else { 715 | dynsecs = append(dynsecs, Dyn{Tag: elf.DT_REL, Val: uint64(base + dynrelPos)}) 716 | dynsecs = append(dynsecs, Dyn{Tag: elf.DT_RELSZ, Val: uint64(dynrelLen)}) 717 | dynsecs = append(dynsecs, Dyn{Tag: elf.DT_RELENT, Val: uint64(relsz)}) 718 | } 719 | 720 | dynsecs = append(dynsecs, []Dyn{ 721 | {Tag: elf.DT_STRTAB, Val: base + dynstrPos}, 722 | {Tag: elf.DT_STRSZ, Val: dynstrLen}, 723 | {Tag: elf.DT_SYMTAB, Val: base + dynsymPos}, 724 | {Tag: elf.DT_SYMENT, Val: dynsymLen}, 725 | {Tag: elf.DT_HASH, Val: hashPos + base}, 726 | {Tag: elf.DT_BIND_NOW, Val: 0}, 727 | {Tag: elf.DT_NULL, Val: 0}, 728 | }...) 729 | 730 | dynsec := f.makeDynsec(dynsecs) 731 | dynsecPos := dynrelPos + dynrelLen 732 | dynsecPos += padding(dynsecPos, 8) 733 | dynsecLen := uint64(len(dynsec)) 734 | 735 | f.e.Progs[dynsecProg].Off = dynsecPos 736 | f.e.Progs[dynsecProg].Vaddr = dynsecPos + base 737 | f.e.Progs[dynsecProg].Paddr = dynsecPos + base 738 | f.e.Progs[dynsecProg].Filesz = dynsecLen 739 | f.e.Progs[dynsecProg].Memsz = dynsecLen 740 | 741 | afterDynsec := dynsecPos + dynsecLen 742 | 743 | relPos := afterDynsec 744 | var torelocate []*elf.Section 745 | relocated := make(map[int]bool) 746 | 747 | for { 748 | var newRelocate []*elf.Section 749 | for i, sec := range f.e.Sections { 750 | if sec.Type == elf.SHT_NULL { 751 | continue 752 | } 753 | if sec.Offset < relPos && !relocated[i] { 754 | newRelocate = append(newRelocate, sec) 755 | relocated[i] = true 756 | } 757 | } 758 | if len(newRelocate) == 0 { 759 | break 760 | } 761 | torelocate = append(torelocate, newRelocate...) 762 | 763 | sort.Slice(torelocate, func(i, j int) bool { return torelocate[i].Offset < torelocate[j].Offset }) 764 | relPos = afterDynsec 765 | for _, sec := range torelocate { 766 | relPos += sec.Size 767 | if sec.Addralign > 1 { 768 | relPos += padding(relPos, sec.Addralign) 769 | } 770 | } 771 | } 772 | 773 | for _, sec := range torelocate { 774 | data := make([]byte, sec.Size) 775 | if _, err := f.f.ReadAt(data, int64(sec.Offset)); err != nil { 776 | log.Fatal(err) 777 | } 778 | if sec.Addralign > 1 { 779 | afterDynsec += padding(afterDynsec, sec.Addralign) 780 | } 781 | if err := f.WriteAt(data, afterDynsec); err != nil { 782 | log.Fatal(err) 783 | } 784 | for _, prog := range f.e.Progs { 785 | if prog.Off == sec.Offset { 786 | prog.Off = afterDynsec 787 | } 788 | if prog.Vaddr == sec.Offset+base { 789 | prog.Vaddr = afterDynsec + base 790 | prog.Paddr = afterDynsec + base 791 | } 792 | } 793 | 794 | sec.Addr += afterDynsec - sec.Offset // or base + offset 795 | sec.Offset, afterDynsec = afterDynsec, afterDynsec+sec.Offset 796 | } 797 | 798 | if afterDynsec > f.e.Entry { 799 | log.Fatal("not enough space before entry point") 800 | } 801 | 802 | if f.e.Progs[baseProg].Filesz < afterDynsec { 803 | f.e.Progs[baseProg].Filesz = afterDynsec 804 | f.e.Progs[baseProg].Memsz = afterDynsec 805 | } 806 | 807 | if err := f.WritePrograms(); err != nil { 808 | log.Fatal(err) 809 | } 810 | 811 | if err := f.WriteAt(interpB, interpPos); err != nil { 812 | log.Fatal(err) 813 | } 814 | 815 | if err := f.WriteAt(hash, hashPos); err != nil { 816 | log.Fatal(err) 817 | } 818 | 819 | if err := f.WriteAt(dynstr, dynstrPos); err != nil { 820 | log.Fatal(err) 821 | } 822 | 823 | if err := f.WriteAt(dynsym, dynsymPos); err != nil { 824 | log.Fatal(err) 825 | } 826 | 827 | if err := f.WriteAt(dynrel, dynrelPos); err != nil { 828 | log.Fatal(err) 829 | } 830 | 831 | if err := f.WriteAt(dynsec, dynsecPos); err != nil { 832 | log.Fatal(err) 833 | } 834 | 835 | f.e.Sections = append(f.e.Sections, &elf.Section{ 836 | SectionHeader: elf.SectionHeader{ 837 | Name: ".interp", 838 | Type: elf.SHT_PROGBITS, 839 | Flags: elf.SHF_ALLOC, 840 | Addr: base + interpPos, 841 | Offset: interpPos, 842 | FileSize: interpLen, 843 | Addralign: 1, 844 | }}) 845 | 846 | dynstrI := len(f.e.Sections) 847 | 848 | f.e.Sections = append(f.e.Sections, &elf.Section{ 849 | SectionHeader: elf.SectionHeader{ 850 | Name: ".dynstr", 851 | Type: elf.SHT_STRTAB, 852 | Flags: elf.SHF_ALLOC, 853 | Addr: base + dynstrPos, 854 | Offset: dynstrPos, 855 | FileSize: dynstrLen, 856 | Addralign: 1, 857 | }}) 858 | 859 | entSize := uint64(24) 860 | if f.e.Class == elf.ELFCLASS32 { 861 | entSize = 16 862 | } 863 | 864 | dynsymSec := len(f.e.Sections) 865 | 866 | f.e.Sections = append(f.e.Sections, &elf.Section{ 867 | SectionHeader: elf.SectionHeader{ 868 | Name: ".dynsym", 869 | Type: elf.SHT_DYNSYM, 870 | Flags: elf.SHF_ALLOC, 871 | Addr: base + dynsymPos, 872 | Offset: dynsymPos, 873 | FileSize: dynsymLen, 874 | Addralign: 8, 875 | Link: uint32(dynstrI), 876 | Entsize: entSize, 877 | Info: uint32(dynsymLocal + 1), 878 | }}) 879 | 880 | entSize = uint64(16) 881 | if f.e.Class == elf.ELFCLASS32 { 882 | entSize = 8 883 | } 884 | 885 | f.e.Sections = append(f.e.Sections, &elf.Section{ 886 | SectionHeader: elf.SectionHeader{ 887 | Name: ".dynamic", 888 | Type: elf.SHT_DYNAMIC, 889 | Flags: elf.SHF_ALLOC | elf.SHF_WRITE, 890 | Addr: base + dynsecPos, 891 | Offset: dynsecPos, 892 | FileSize: dynsecLen, 893 | Addralign: 8, 894 | Link: uint32(dynstrI), 895 | Entsize: entSize, 896 | }}) 897 | 898 | dynname := ".rel" 899 | if rela { 900 | dynname = ".rela" 901 | } 902 | dynname += f.e.Sections[symsection].Name 903 | 904 | shtype := elf.SHT_REL 905 | if rela { 906 | shtype = elf.SHT_RELA 907 | } 908 | 909 | f.e.Sections = append(f.e.Sections, &elf.Section{ 910 | SectionHeader: elf.SectionHeader{ 911 | Name: dynname, 912 | Type: shtype, 913 | Flags: elf.SHF_ALLOC, 914 | Addr: base + dynrelPos, 915 | Offset: dynrelPos, 916 | FileSize: dynrelLen, 917 | Addralign: 8, 918 | Link: uint32(dynsymSec), 919 | Info: uint32(symsection), 920 | Entsize: relsz, 921 | }}) 922 | 923 | f.e.Sections = append(f.e.Sections, &elf.Section{ 924 | SectionHeader: elf.SectionHeader{ 925 | Name: ".hash", 926 | Type: elf.SHT_HASH, 927 | Flags: elf.SHF_ALLOC, 928 | Addr: base + hashPos, 929 | Offset: hashPos, 930 | FileSize: hashLen, 931 | Addralign: 8, 932 | Link: uint32(dynsymSec), 933 | }}) 934 | 935 | shoff, err := f.f.Seek(0, io.SeekEnd) 936 | if err != nil { 937 | log.Fatal(err) 938 | } 939 | f.shoff = uint64(shoff) 940 | 941 | if err := f.WriteSections(); err != nil { 942 | log.Fatal(err) 943 | } 944 | 945 | f.Close() 946 | } 947 | --------------------------------------------------------------------------------