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: run time crash calling Windows functions #23533

Closed
unsacrificed opened this issue Jan 24, 2018 · 3 comments
Closed

cmd/cgo: run time crash calling Windows functions #23533

unsacrificed opened this issue Jan 24, 2018 · 3 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

@unsacrificed
Copy link

unsacrificed commented Jan 24, 2018

go version go1.10beta2 windows/amd64 (also try 1.9.3 on windows/amd64).

go env:

set GOARCH=amd64
set GOBIN=
set GOCACHE=C:\Users\unsac\AppData\Local\go-build
set GOEXE=.exe
set GOHOSTARCH=amd64
set GOHOSTOS=windows
set GOOS=windows
set GOPATH=C:\Users\unsac\go
set GORACE=
set GOROOT=C:\Go
set GOTMPDIR=
set GOTOOLDIR=C:\Go\pkg\tool\windows_amd64
set GCCGO=gccgo
set CC=gcc
set CXX=g++
set CGO_ENABLED=1
set CGO_CFLAGS=-g -O2
set CGO_CPPFLAGS=
set CGO_CXXFLAGS=-g -O2
set CGO_FFLAGS=-g -O2
set CGO_LDFLAGS=-g -O2
set PKG_CONFIG=pkg-config
set GOGCCFLAGS=-m64 -mthreads -fmessage-length=0 -fdebug-prefix-map=C:\Users\unsac\AppData\Local\Temp\go-build899159378=/tmp/go-buil
d -gno-record-gcc-switches

What did you do?

I use cgo to work with Microsoft Direct2D API. All looks ok until I call ID2D1HwndRenderTarget's "method" GetSize or GetPixelSize:

D2D1_SIZE_F ID2D1HwndRenderTarget_GetSizeCgo(ID2D1HwndRenderTarget *This){
	return ID2D1HwndRenderTarget_GetSize(This);
}

D2D1_SIZE_U ID2D1HwndRenderTarget_GetPixelSizeCgo(ID2D1HwndRenderTarget *This){
	return ID2D1HwndRenderTarget_GetPixelSize(This);
}

void SetPixelUnitMode(
	ID2D1HwndRenderTarget *This
){
	D2D1_SIZE_U Pixels = ID2D1HwndRenderTarget_GetPixelSize(This);
	D2D1_SIZE_F DPIs = ID2D1HwndRenderTarget_GetSize(This);
	D2D1_MATRIX_3X2_F m;
	if (Pixels.width == 0 || Pixels.height == 0){
		m = IdentityMatrix();	// Just avoid division by zero
	} else {
		m = ScaledMatrix(DPIs.width/(float)Pixels.width, DPIs.height/(float)Pixels.height);
	}

	ID2D1HwndRenderTarget_SetTransform(This, &m);
}
func HwndRenderTargetGetPixelSize(target WindowRenderTargetP)PointU32{
	r := C.ID2D1HwndRenderTarget_GetPixelSizeCgo((*C.ID2D1HwndRenderTarget)(target))
	return *(*PointU32)(unsafe.Pointer(&r))
}

func HwndRenderTargetGetSize(target WindowRenderTargetP)PointF{
	r := C.ID2D1HwndRenderTarget_GetSizeCgo((*C.ID2D1HwndRenderTarget)(target))
	return *(*PointF)(unsafe.Pointer(&r))
}

func WindowRenderTargetSetPixelUnitMode(target WindowRenderTargetP) {
	C.SetPixelUnitMode((*C.ID2D1HwndRenderTarget)(target))
}

Call to any of these functions causes runtime crash (reproduced each time). Following errors I get with Go 1.10 beta2 (with 1.9.3 there is less information, but them is the of same kind - "stack split at bad time" & "Exception" with registers).
GetSize:

runtime: newstack at runtime.sigpanic+0x177 sp=0xc042087db8 stack=[0xc042080000, 0xc042088000]
	morebuf={pc:0x450088 sp:0xc042087dc0 lr:0x0}
	sched={pc:0x43a9d7 sp:0xc042087db8 lr:0x0 ctxt:0x0}
runtime.asmcgocall(0x553c50, 0xc042087e30)
	C:/Go/src/runtime/asm_amd64.s:698 +0x98 fp=0xc042087dc8 sp=0xc042087dc0 pc=0x450088
fatal error: runtime: stack split at bad time

runtime stack:
runtime.throw(0x4e17b5, 0x20)
	C:/Go/src/runtime/panic.go:616 +0x88 fp=0x88fc40 sp=0x88fc20 pc=0x42a2a8
runtime.newstack()
	C:/Go/src/runtime/stack.go:954 +0xb6f fp=0x88fdd0 sp=0x88fc40 pc=0x43dd9f
runtime.morestack()
	C:/Go/src/runtime/asm_amd64.s:480 +0x91 fp=0x88fdd8 sp=0x88fdd0 pc=0x44e8e1

goroutine 1 [syscall]:
runtime.asmcgocall(0x553c50, 0xc042087e30)
	C:/Go/src/runtime/asm_amd64.s:698 +0x98 fp=0xc042087dc8 sp=0xc042087dc0 pc=0x450088

goroutine 2 [force gc (idle)]:
runtime.gopark(0x4e46f0, 0x570ac0, 0x4dd5e5, 0xf, 0x4e4614, 0x1)
	C:/Go/src/runtime/proc.go:291 +0x12f fp=0xc04203bf68 sp=0xc04203bf48 pc=0x42bd4f
runtime.goparkunlock(0x570ac0, 0x4dd5e5, 0xf, 0x14, 0x1)
	C:/Go/src/runtime/proc.go:297 +0x65 fp=0xc04203bfa8 sp=0xc04203bf68 pc=0x42be15
runtime.forcegchelper()
	C:/Go/src/runtime/proc.go:248 +0xda fp=0xc04203bfe0 sp=0xc04203bfa8 pc=0x42bb7a
runtime.goexit()
	C:/Go/src/runtime/asm_amd64.s:2361 +0x1 fp=0xc04203bfe8 sp=0xc04203bfe0 pc=0x450e71
created by runtime.init.4
	C:/Go/src/runtime/proc.go:237 +0x3c

goroutine 3 [GC sweep wait]:
runtime.gopark(0x4e46f0, 0x570ba0, 0x4dcf0f, 0xd, 0x41df14, 0x1)
	C:/Go/src/runtime/proc.go:291 +0x12f fp=0xc042037f60 sp=0xc042037f40 pc=0x42bd4f
runtime.goparkunlock(0x570ba0, 0x4dcf0f, 0xd, 0x14, 0x1)
	C:/Go/src/runtime/proc.go:297 +0x65 fp=0xc042037fa0 sp=0xc042037f60 pc=0x42be15
runtime.bgsweep(0xc042014070)
	C:/Go/src/runtime/mgcsweep.go:52 +0xb1 fp=0xc042037fd8 sp=0xc042037fa0 pc=0x41dff1
runtime.goexit()
	C:/Go/src/runtime/asm_amd64.s:2361 +0x1 fp=0xc042037fe0 sp=0xc042037fd8 pc=0x450e71
created by runtime.gcenable
	C:/Go/src/runtime/mgc.go:216 +0x5f

goroutine 4 [finalizer wait]:
runtime.gopark(0x4e46f0, 0x58ee08, 0x4dd29a, 0xe, 0x14, 0x1)
	C:/Go/src/runtime/proc.go:291 +0x12f fp=0xc042039f18 sp=0xc042039ef8 pc=0x42bd4f
runtime.goparkunlock(0x58ee08, 0x4dd29a, 0xe, 0x14, 0x1)
	C:/Go/src/runtime/proc.go:297 +0x65 fp=0xc042039f58 sp=0xc042039f18 pc=0x42be15
runtime.runfinq()
	C:/Go/src/runtime/mfinal.go:175 +0xbb fp=0xc042039fe0 sp=0xc042039f58 pc=0x41509b
runtime.goexit()
	C:/Go/src/runtime/asm_amd64.s:2361 +0x1 fp=0xc042039fe8 sp=0xc042039fe0 pc=0x450e71
created by runtime.createfing
	C:/Go/src/runtime/mfinal.go:156 +0x69

GetPixelSize:

Exception 0xc0000005 0x8 0x1f4000001f4 0x1f4000001f4
PC=0x1f4000001f4

gui/drivers/windows/d2d/d2dc._Cfunc_ID2D1HwndRenderTarget_GetPixelSizeCgo(0x6343700, 0xc042093dc0)
	_cgo_gotypes.go:583 +0x51
gui/drivers/windows/d2d/d2dc.HwndRenderTargetGetPixelSize.func1(0x6343700, 0xc042060084)
	C:/Users/unsac/go/src/gui/drivers/windows/d2d/d2dc/window-render-target.go:26 +0x5d
gui/drivers/windows/d2d/d2dc.HwndRenderTargetGetPixelSize(0x6343700, 0x70ca6)
	C:/Users/unsac/go/src/gui/drivers/windows/d2d/d2dc/window-render-target.go:26 +0x32
gui/drivers/windows/d2d.New(0x1f4, 0x1f4, 0x4dc672, 0x9, 0x4ccc60, 0xc042093f78, 0x405483)
	C:/Users/unsac/go/src/gui/drivers/windows/d2d/window.go:41 +0x171
main.main()
	C:/Users/unsac/go/src/gui/main.go:17 +0x61
rax     0x0
rbx     0xc042093e30
rcx     0x571518
rdi     0xc042034000
rsi     0xc042093dc0
rbp     0xc042093df0
rsp     0xc042093dc8
r8      0xc042034000
r9      0x0
r10     0x0
r11     0x246
r12     0xffffffffffffffff
r13     0x8
r14     0x7
r15     0x400
rip     0x1f4000001f4
rflags  0x10206
cs      0x33
fs      0x53
gs      0x2b

I expect no panic.

Additional information:
I'm sure that target/This is not nil/NULL.
I use TDM-GCC-64 5.1.0-2.
I use my own *Cgo C functions (i.e. ID2D1HwndRenderTarget_GetSizeCgo) because in most cases I unable to use 3d-party C functions directly (could not determine kind of name for C.ID2D1HwndRenderTarget_GetPixelSize, I dont know why).

@ianlancetaylor
Copy link
Contributor

Can you show us a complete standalone program that we can use to recreate the problem?

This looks a lot like the C code is dereferencing an invalid pointer and crashing the program. It's hard to be sure, though.

@ianlancetaylor ianlancetaylor changed the title Crashes on Cgo calls. cmd/cgo: run time crash calling Windows functions Jan 24, 2018
@ianlancetaylor ianlancetaylor added the NeedsInvestigation Someone must examine and confirm this is a valid issue and not a duplicate of an existing one. label Jan 24, 2018
@ianlancetaylor ianlancetaylor added this to the Go1.11 milestone Jan 24, 2018
@unsacrificed
Copy link
Author

Code for reproduce (checked on Windows 10 amd64):
main.go

package main

import (
	"syscall"
	"unsafe"
)

/*
#cgo LDFLAGS: -lD2d1
#include "main.h"
*/
import "C"

const (
	className = "someClass"
	width     = 500
	height    = 500
)

const sOk       = 0

const (
	WMDestroy = 0x00000002
	WMClose   = 0x00000010
	WMPaint   = 0x0000000f
)

var (
	factory unsafe.Pointer
	target  unsafe.Pointer
)

func main() {

	//
	// Convert class name
	//

	windowClassNameUtf16, err := syscall.UTF16PtrFromString(className)
	if err != nil {
		panic(err.Error())
	}

	//
	// Create window
	//

	var window unsafe.Pointer
	window = unsafe.Pointer(C.CreateWindowCgo(C.LPCWSTR(unsafe.Pointer(windowClassNameUtf16)), C.WNDPROC(unsafe.Pointer(syscall.NewCallback(procudere))), width, height))
	if window == nil {
		panic("Unable create window")
	}

	//
	// Create Direct2D factory
	//

	var tmpFactory unsafe.Pointer // use "tmpFactory" instead of global "factory" because of "cgo argument has Go pointer to Go pointer". Another BUG ???
	errI := C.D2D1CreateFactoryCgo((*unsafe.Pointer)(unsafe.Pointer(&tmpFactory)))
	if errI != sOk {
		panic("Unable create factory")
	}
	factory = tmpFactory

	//
	// Create Direct2D window render target
	//

	var tmpTarget unsafe.Pointer // use "tmpTarget" instead of global "target" because of "cgo argument has Go pointer to Go pointer". Another BUG ???
	errI = C.ID2D1Factory_CreateHwndRenderTargetCgo(
		(*C.ID2D1Factory)(factory),
		C.HWND(window),
		(**C.ID2D1HwndRenderTarget)(unsafe.Pointer(&tmpTarget)),
	)
	if errI != sOk {
		panic("Unable create window render target")
	}
	target = tmpTarget

	//
	// Check for BUG
	//

	if target == nil { // Special check to be 100% sure
		panic("Nil target")
	}

	_ = C.ID2D1HwndRenderTarget_GetSizeCgo((*C.ID2D1HwndRenderTarget)(target))      // Here crash
	_ = C.ID2D1HwndRenderTarget_GetPixelSizeCgo((*C.ID2D1HwndRenderTarget)(target)) // And here crash too
	C.ID2D1HwndRenderTarget_DrawLineCgo((*C.ID2D1HwndRenderTarget)(target))         // But this works

	//
	// Enter main event loop
	//

	C.MainLoop()
}

func procudere(window unsafe.Pointer, msg uint32, wParam uint64, lParam int64) int64 {
	switch msg {
	case WMClose:
		C.ID2D1HwndRenderTarget_ReleaseCgo((*C.ID2D1HwndRenderTarget)(target))
		C.ID2D1Factory_ReleaseCgo((*C.ID2D1Factory)(factory))
		C.DestroyWindow(C.HWND(window))
		return 0
	case WMDestroy:
		C.PostQuitMessage(0)
		return 0
	case WMPaint:
		if target != nil {
			C.ID2D1HwndRenderTarget_DrawLineCgo((*C.ID2D1HwndRenderTarget)(target))
		}
		return 0
	default:
		return int64(C.DefWindowProcW(
			C.HWND(window),
			C.UINT(msg),
			C.WPARAM(wParam),
			C.LPARAM(lParam),
		))
	}
}

main.h

#define INITGUID
#include <Windows.h>
#include <D2d1.h>

HWND CreateWindowCgo(LPCWSTR windowClassName, WNDPROC windowProc, int width, int height)
{
    HMODULE hModule;
    if(!GetModuleHandleExW(GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT,NULL,&hModule)){
        return NULL;
    }

    WNDCLASSEXW windowClass;
    windowClass.cbSize=sizeof(WNDCLASSEXW);
    windowClass.style=0;
    windowClass.lpfnWndProc=windowProc;
    windowClass.cbClsExtra=0;
    windowClass.cbWndExtra=0;
    windowClass.hInstance=hModule;
    windowClass.hIcon=NULL;
    windowClass.hCursor=NULL;
    windowClass.hbrBackground=NULL;
    windowClass.lpszMenuName=NULL;
    windowClass.lpszClassName=windowClassName;
    windowClass.hIconSm=NULL;

    ATOM windowClassAtom = RegisterClassExW(&windowClass);
    if (windowClassAtom==0){
        return NULL;
    }

    HWND window = CreateWindowExW(
      0,                                            //    In    DWORD        dwExStyle
      (LPCWSTR)(MAKEINTRESOURCE(windowClassAtom)),  //    In    LPCTSTR      lpClassName
      NULL,                                         //    In    LPCTSTR      lpWindowName
      WS_VISIBLE,                                   //    In    DWORD        dwStyle
      CW_USEDEFAULT,                                //    In    int          x
      CW_USEDEFAULT,                                //    In    int          y
      width,                                        //    In    int          nWidth
      height,                                       //    In    int          nHeight
      NULL,                                         //    In    HWND         hWndParent
      NULL,                                         //    In    HMENU        hMenu
      NULL,                                         //    In    HINSTANCE    hInstance
      NULL                                          //    In    LPVOID       lpParam
    );

    return window;
}

void MainLoop(){
    MSG msg;

    while( TRUE ) {
        switch (GetMessage( &msg, NULL, 0, 0 )){
        case -1:
        case 0:
            return;
        default:
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
    }
}


HRESULT D2D1CreateFactoryCgo(void **ppIFactory){
    D2D1_FACTORY_OPTIONS options;
    options.debugLevel = D2D1_DEBUG_LEVEL_WARNING;

    return D2D1CreateFactory(
        D2D1_FACTORY_TYPE_SINGLE_THREADED,
        &IID_ID2D1Factory,
        &options,
        ppIFactory
    );
}

HRESULT ID2D1Factory_CreateHwndRenderTargetCgo(
    ID2D1Factory *This,
    HWND hwnd,
    ID2D1HwndRenderTarget **hwndRenderTarget
){
    D2D1_RENDER_TARGET_PROPERTIES properties;
    properties.type = D2D1_RENDER_TARGET_TYPE_DEFAULT;
    properties.pixelFormat.format = DXGI_FORMAT_B8G8R8A8_UNORM;
    properties.pixelFormat.alphaMode = D2D1_ALPHA_MODE_PREMULTIPLIED;
    properties.dpiX = 0;
    properties.dpiY = 0;
    properties.usage = D2D1_RENDER_TARGET_USAGE_NONE;
    properties.minLevel = D2D1_FEATURE_LEVEL_DEFAULT;

    RECT rc;
    GetClientRect(hwnd, &rc);

    D2D1_SIZE_U size;
    size.width = rc.right - rc.left;
    size.height = rc.bottom - rc.top;

    D2D1_HWND_RENDER_TARGET_PROPERTIES hwndProperties;
    hwndProperties.hwnd = hwnd;
    hwndProperties.pixelSize = size;
    hwndProperties.presentOptions = D2D1_PRESENT_OPTIONS_NONE;

    return ID2D1Factory_CreateHwndRenderTarget(
        This,
        &properties,
        &hwndProperties,
        hwndRenderTarget
    );
}

D2D1_SIZE_F ID2D1HwndRenderTarget_GetSizeCgo(ID2D1HwndRenderTarget *This){
    return ID2D1HwndRenderTarget_GetSize(This);
}

D2D1_SIZE_U ID2D1HwndRenderTarget_GetPixelSizeCgo(ID2D1HwndRenderTarget *This){
    return ID2D1HwndRenderTarget_GetPixelSize(This);
}

void ID2D1HwndRenderTarget_DrawLineCgo(ID2D1HwndRenderTarget *This){
    ID2D1HwndRenderTarget_BeginDraw(This);

    D2D1_POINT_2F point0;
    point0.x = 0;
    point0.y = 0;
    D2D1_POINT_2F point1;
    point1.x = 200;
    point1.y = 200;

    FLOAT strokeWidth = 1;

    D2D1_COLOR_F color;
    color.r = 1;
    color.a = 0.5;

    D2D1_BRUSH_PROPERTIES properties;
    properties.opacity = 1.0f;
    properties.transform._11 = 1;
    properties.transform._12 = 0;
    properties.transform._21 = 0;
    properties.transform._22 = 1;
    properties.transform._31 = 0;
    properties.transform._32 = 0;

    ID2D1SolidColorBrush *brush;
    ID2D1HwndRenderTarget_CreateSolidColorBrush(This, &color, &properties, &brush);

    ID2D1HwndRenderTarget_DrawLine(This,point0,point1,(ID2D1Brush *)brush,strokeWidth,NULL);

    ID2D1SolidColorBrush_Release(brush);

    ID2D1HwndRenderTarget_EndDraw(This,NULL,NULL);
}

ULONG ID2D1HwndRenderTarget_ReleaseCgo(ID2D1HwndRenderTarget *This){ return ID2D1HwndRenderTarget_Release(This); }

ULONG ID2D1Factory_ReleaseCgo(ID2D1Factory *This){ return ID2D1Factory_Release(This); }

@ianlancetaylor
Copy link
Contributor

Your code appears to be converting Windows HANDLE values to Go unsafe.Pointer values. That is not a safe operation. A windows HANDLE is not a pointer. A Go unsafe.Pointer must always be a pointer. When you store a value that is not a pointer into an unsafe.Pointer, you can confuse the garbage collector, and the stack copier, and corrupt memory.

I'm going to close this since it looks like that is the problem here. Please comment if you disagree.

@golang golang locked and limited conversation to collaborators Jun 13, 2019
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

3 participants