Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

cmd/cgo: Go-owned value zeroed after C call returns #27656

Closed
nilsocket opened this issue Sep 13, 2018 · 12 comments
Closed

cmd/cgo: Go-owned value zeroed after C call returns #27656

nilsocket opened this issue Sep 13, 2018 · 12 comments

Comments

@nilsocket
Copy link
Contributor

What version of Go are you using (go version)?

go version go1.11 linux/amd64

What operating system and processor architecture are you using (go env)?

GOARCH="amd64"
GOOS="linux"
Linux nil 4.18.6-arch1-1-ARCH #1 SMP PREEMPT GNU/Linux

What did you do?

main.go

  • go build -buildmode=c-shared -o libtemp.so; cp *.h *.so ../
package main

import (
	"C"
	"unsafe"
)
import "reflect"

func main() {
}

//export phew
func phew() uintptr {
	res := make([]uint8, 2)
	res[0] = uint8(1)
	res[1] = uint8(2)
	// time.Sleep(time.Millisecond)
	hdr := (*reflect.SliceHeader)(unsafe.Pointer(&res))
	return hdr.Data
}

main.c

  • gcc main.c -ltemp -L. -Wl,-R. ; ./a.out
#include <stdio.h>
#include <inttypes.h>

#include "libtemp.h"

int main(){
	uintptr_t resPtr = phew();
	uint8_t *res = (uint8_t*)resPtr;

	for (int i = 0; i < 2; i++){
		printf("%d\n", res[i]);
	}

	printf("Exiting gracefully");
}

What did you expect to see?

1
2
Exiting gracefully

What did you see instead?

0
0
Exiting gracefully

un-commenting time.Sleep(time.Millisecond), gives correct result.

@nilsocket nilsocket changed the title CGO: returning from function before doing work CGO: returning from function before work is done Sep 13, 2018
@nilsocket
Copy link
Contributor Author

gcc version if helpful,
gcc (GCC) 8.2.1 20180831

@bcmills
Copy link
Contributor

bcmills commented Sep 13, 2018

cgo-exported functions are not allowed to return pointers to Go memory. (The Go garbage collector does not see the C stack or heap.)

See https://golang.org/cmd/cgo/#hdr-Passing_pointers.

@bcmills bcmills closed this as completed Sep 13, 2018
@bcmills bcmills changed the title CGO: returning from function before work is done cmd/cgo: returning from function before work is done Sep 13, 2018
@bcmills bcmills changed the title cmd/cgo: returning from function before work is done cmd/cgo: Go-owned value zeroed after C call returns Sep 13, 2018
@nilsocket
Copy link
Contributor Author

nilsocket commented Sep 13, 2018

Go code may pass a Go pointer to C provided the Go memory to which it points does not contain any Go pointers.

@bcmills
The pointer which I'm passing doesn't contain any Go Pointer.
C code also doesn't pass around Go pointer anywhere else.
Go-owned value is not being zeroed, as stated in the title.

I don't think the problem is related to GC, as I was able to see result when sleeping by one millisecond.
I assume(not sure), a seperate goroutine is being started to assign values, and by the time it assigns it is returning, or some optimization bug, or cgo bug.

@bcmills
Copy link
Contributor

bcmills commented Sep 13, 2018

	res := make([]uint8, 2)

allocates a slice in Go memory.

	return hdr.Data

returns a Go-owned pointer as a uintptr. The prohibition on passing Go pointers applies even if those pointers are represented as uintptr.

@bcmills
Copy link
Contributor

bcmills commented Sep 13, 2018

The relevant part of the cgo rule is this:

A Go function called by C code may not return a Go pointer (which implies that it may not return a string, slice, channel, and so forth). A Go function called by C code may take C pointers as arguments, and it may store non-pointer or C pointer data through those pointers, but it may not store a Go pointer in memory pointed to by a C pointer. A Go function called by C code may take a Go pointer as an argument, but it must preserve the property that the Go memory to which it points does not contain any Go pointers.

@nilsocket
Copy link
Contributor Author

Note that values of some Go types, other than the type's zero value, always include Go pointers. This is true of string, slice, interface, channel, map, and function types.

Source which you have pointed out means these types contain Go pointers.

Go pointer (which implies that it may not return a string, slice, channel, and so forth)

@nilsocket
Copy link
Contributor Author

@bcmills consider re-opening the issue.

@nilsocket
Copy link
Contributor Author

Go code may pass a Go pointer to C provided the Go memory to which it points does not contain any Go pointers.

@bcmills
The pointer which I'm passing doesn't contain any Go Pointer.
C code also doesn't pass around Go pointer anywhere else.
Go-owned value is not being zeroed, as stated in the title.

I don't think the problem is related to GC, as I was able to see result when sleeping by one millisecond.
I assume(not sure), a seperate goroutine is being started to assign values, and by the time it assigns it is returning, or some optimization bug, or cgo bug.

  • The pointer which I'm passing doesn't contain any other Go Pointer.
	res := make([]uint8, 2)

allocates a slice in Go memory.

	return hdr.Data

returns a Go-owned pointer as a uintptr. The prohibition on passing Go pointers applies even if those pointers are represented as uintptr.

But the pointer which I'm passing doesn't contain any other Go pointer, which means I can pass it to C.

@ianlancetaylor
Copy link
Contributor

The rule, which @bcmills quoted above is: "A Go function called by C code may not return a Go pointer." You are looking at the rule for pointers that may be passed from Go code when calling C code. That is not what your program is doing. Your program is C code calling a Go function. That rule for that case is that the Go function may not return a Go pointer. It's not OK for an exported Go function to return a Go pointer that does not point to other Go pointers; an exported Go function may not return a Go pointer at all.

@nilsocket
Copy link
Contributor Author

@ianlancetaylor You cleared the point, but

A Go function called by C code may take a Go pointer as an argument, but it must preserve the property that the Go memory to which it points does not contain any Go pointers.

If a Go function which is called by C code can take a Go pointer as argument, then how did the C program get the Go pointer in first place.

@bcmills
Copy link
Contributor

bcmills commented Sep 13, 2018

If a Go function which is called by C code can take a Go pointer as argument, then how did the C program get the Go pointer in first place.

The C function gets the Go pointer by itself being called by a Go function. (If you have a Go→C→Go call chain, the values passed in the Go→C call are pinned for the duration and therefore available for the C→Go call, since the C→Go call must return before the Go→C call can return.)

@nilsocket
Copy link
Contributor Author

@bcmills thanks a lot for clearing up.

@golang golang locked and limited conversation to collaborators Sep 13, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests

4 participants