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: no console output after invoking a go c-shared dll via Windows Powershell #44876

Closed
nwoodmsft opened this issue Mar 9, 2021 · 9 comments
Labels
FrozenDueToAge NeedsInvestigation Someone must examine and confirm this is a valid issue and not a duplicate of an existing one. OS-Windows
Milestone

Comments

@nwoodmsft
Copy link

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

$ go version
go version go1.14 linux/amd64

Does this issue reproduce with the latest release?

Uncertain.

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

go env Output
$ go env

GOARCH="amd64"
GOHOSTARCH="amd64"
GOHOSTOS="linux"
GOOS="linux"
GCCGO="gccgo"
CC="gcc"
CXX="g++"
CGO_ENABLED="1"
CGO_CFLAGS="-g -O2"
CGO_CPPFLAGS=""
CGO_CXXFLAGS="-g -O2"
CGO_FFLAGS="-g -O2"
CGO_LDFLAGS="-g -O2"
GOGCCFLAGS="-fPIC -m64 -pthread -fmessage-length=0 -fdebug-prefix-map=/tmp/go-build351334670=/tmp/go-build -gno-record-gcc-switches"

What did you do?

I cross-compiled a very simple c-shared dll for Windows with one exported function (HelloWorld). I then invoked the dll from Powershell.

The go code was:

package main

import "C"
import (
        "fmt"
)

//export HelloWorld
func HelloWorld() {
        fmt.Println("Hello World")
}

func main() {}

The build command (from linux) used was:

GOARCH=amd64 GOOS=windows CGO_ENABLED=1 CC=x86_64-w64-mingw32-gcc go build -v -buildmode=c-shared -o helloworld.dll .

I then copied the resultant .dll to Windows and invoked the HelloWorld() function using the following Powershell script:

$signature = @"
[DllImport("c:\\cshared\\helloworld.dll")]
public static extern void HelloWorld();
"@

Add-Type -MemberDefinition $signature -Name World -Namespace Hello

[Hello.World]::HelloWorld()

What did you expect to see?

I expected the dll's HelloWorld() function to be invoked successfully (causing the text "Hello World" to be written to the console) and then I expected to be able to use my powershell prompt to execute further console commands such as "hostname" or "ipconfig" etc.

What did you see instead?

This is really odd. The HelloWorld() function is indeed executed successfully and I see the text "Hello World" on the console. However, after the function has been invoked, I am no longer able to see any console output from cmd.exe commands in my powershell window. For example, calling "hostname" just returns with no output and the same happens with "ipconfig".

e.g. initially hostname.exe works and provides output, but after executing the cshared dll I am unable to see any further output from hostname..exe on the console:

PS C:\cshared> hostname
nwood-host
PS C:\cshared> hostname
nwood-host
PS C:\cshared> .\helloworld.ps1
Hello World
PS C:\cshared> hostname
PS C:\cshared> ipconfig
PS C:\cshared> hostname

If I pipe the output of hostname.exe to a file (e.g. "hostname > out.txt") then I can confirm that the output is present and the command is succeeding....so it seems like executing the c-shared dll somehow causes stdout to be redirected or not shown properly?

I tried removing the fmt.Println from the go code to see if that helps, but the issues is still the same. I am also only seeing this with Powershell (if I do the same experiment but invoke the helloworld.dll from python on Windows, I do not see any issue afterwards).

Any ideas?

@networkimprov
Copy link

cc @alexbrainman @zx2c4

@gopherbot add OS-Windows

@toothrot toothrot changed the title No console output after invoking a go c-shared dll via Windows Powershell ? cmd/cgo: no console output after invoking a go c-shared dll via Windows Powershell Mar 11, 2021
@toothrot toothrot added this to the Backlog milestone Mar 11, 2021
@toothrot toothrot added the NeedsInvestigation Someone must examine and confirm this is a valid issue and not a duplicate of an existing one. label Mar 11, 2021
@toothrot
Copy link
Contributor

/cc @ianlancetaylor

@toothrot
Copy link
Contributor

@nwoodmsft Can you try to reproduce this issue on Go 1.16?

@DHowett
Copy link

DHowett commented Mar 12, 2021

Investigation results (hi! I own the Windows Console 😄)

The go runtime disables handle inheritance for stdin/stdout/stderr on Windows.

Stdout = getStdHandle(STD_OUTPUT_HANDLE)

func getStdHandle(h int) (fd Handle) {
r, _ := GetStdHandle(h)
CloseOnExec(r)
return r
}

func CloseOnExec(fd Handle) {
SetHandleInformation(Handle(fd), HANDLE_FLAG_INHERIT, 0)
}

When the runtime is loaded in a DLL, it immediately disables the inheritance of stdin/stdout/stderr. 😊

@nwoodmsft
Copy link
Author

Thank you so much for taking a look @DHowett ! @alexbrainman It looks like a fix is needed in golang to address this. I did try on 1.16 and the issue was the same.

@DHowett Until a fix can be made, is there any workaround we can do after the p/invoke to "repair" a console which is in this state? My current workaround is to execute the PS cmdlet in a new session (i.e. "$result = Powershell.exe -command { Hello-World }" or to execute it as a PS job potentially.

@DHowett
Copy link

DHowett commented Mar 12, 2021

A harmless workaround would be to P/Invoke SetHandleInformation to restore inheritance to those three handles. 😄

@zx2c4
Copy link
Contributor

zx2c4 commented Mar 12, 2021

Previously we likely twiddled with inheritance because our invocations of CreateProcess were unsafe. I've fixed that for 1.16 already. That means we can safely stop changing stdio handle inheritance.

I'll send a CL.

@alexbrainman
Copy link
Member

@nwoodmsft

Thank you for creating this issue.

I can reproduce your problem here.

I am trying to add new syscall test that fails because of this issue. Unfortunately I don't know much about Powershell.

If I put this

hostname

$signature = @"
[DllImport("c:\helloworld.dll")]
public static extern void HelloWorld();
"@

Add-Type -MemberDefinition $signature -Name World -Namespace Hello

[Hello.World]::HelloWorld()

hostname

into a file, like a.ps1. And then run this command

powershell -Command "c:\a.ps1"

I get correct output

sharik
Hello World

Unfortunately I had to run this powershell command set-executionpolicy remotesigned, otherwise I get this error message:

c:\a.ps1 : File U:\rw\helloworld.ps1 cannot be loaded because running scripts is disabled on this system. For
more information, see about_Execution_Policies at https:/go.microsoft.com/fwlink/?LinkID=135170.
At line:1 char:1
+ c:\a.ps1
+ ~~~~~~~~~~~~~~~~
    + CategoryInfo          : SecurityError: (:) [], PSSecurityException
    + FullyQualifiedErrorId : UnauthorizedAccess

I plan to run this test on other people computers. So I won't be able to run set-executionpolicy remotesigned command there, because that requires Administrator access.

Is there any way to run your Powershell script on command line or something? For example, I can run multiple commands there delimited by ;. Unfortunetly I don't know how to fit your $signature string there.

Alternatively, do you know of a way to reproduce your program with a C program built with Mingw GCC? That would be even better, because this way we don't need to rely on Powershell.

Thank you.

Alex

@gopherbot
Copy link

Change https://golang.org/cl/316269 mentions this issue: syscall: do not change stdio handle inheritance

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
FrozenDueToAge NeedsInvestigation Someone must examine and confirm this is a valid issue and not a duplicate of an existing one. OS-Windows
Projects
None yet
Development

No branches or pull requests

7 participants