Opened 4 years ago

Closed 3 years ago

#5254 closed bug (fixed)

usb library fails on Windows

Reported by: basvandijk Owned by: simonmar
Priority: high Milestone: 7.6.1
Component: Compiler (FFI) Version: 7.0.3
Keywords: Cc: Joris Putcuyps <joris.putcuyps@…>, John Obbele <john.obbele@…>, Maurício Antunes <mauricio.antunes@…>
Operating System: Windows Architecture: Unknown/Multiple
Type of failure: Runtime crash Test Case:
Blocked By: Blocking:
Related Tickets: Differential Revisions:

Description

I'm trying to get my Haskell usb library to work on Windows. I currently get a weird error. Please follow the steps below to reproduce the error:

(Important build-time files: libusb-1.0\include\libusb.h and MinGW32\dll\libusb-1.0.dll.a)

(Important run-time files: MinGW32\dll\libusb-1.0.dll)

  • Download, unpack and cabal install the, as of yet unreleased, bindings-DSL-1.0.12 (This version lets you use the right calling convention on Windows (stdcall instead of ccall) by configuring cabal with cc-options: -DBINDINGS_STDCALLCONV)
  • Install bindings-libusb:
    git clone git://github.com/basvandijk/bindings-libusb.git
    cd bindings-libusb
    

Make sure to checkout the windows branch and let cabal know where to find libusb:

git checkout windows
cabal install --extra-include-dirs="C:\Program Files\libusb\libusb1\include\libusb-1.0" 
              --extra-lib-dirs="C:\Program Files\libusb\libusb1\MinGW32\dll"
  • Clone the usb repository:
    git clone git://github.com/basvandijk/usb.git
    cd usb
    
    There's no need to install the library. I included an example program that will demonstrate the error:
    cabal configure --flags="example -library"
    cabal build
    

The example should read some bytes of an attached USB mouse (change the VID and PID to match your mouse). However it gives two errors:

dist\build\example\example.exe
example.exe: NotFoundException
Segmentation fault/access violation in generated code

This bug report is about the segmentation fault.

The NotFoundException is thrown by c'libusb_get_config_descriptor
which is indirectly called by getDevices. According to the
libusb docs
this error is thrown when the specified configuration doesn't
exists. Since I only call this function on existing configurations
this seems like a bug in libusb. I will dive in the libusb source code to see what is going on.

The Segmentation fault/access violation in generated code is caused
by the finalizer in newCtx:

newCtx ∷ IO Ctx
newCtx = newCtxNoEventManager Ctx

libusb_init ∷ IO (Ptr C'libusb_context)
libusb_init = alloca $ \ctxPtrPtr → do
               handleUSBException $ c'libusb_init ctxPtrPtr
               peek ctxPtrPtr

newCtxNoEventManager ∷ (ForeignPtr  C'libusb_context → Ctx) → IO Ctx
newCtxNoEventManager ctx = mask_ $ do
                            ctxPtr ← libusb_init
                            ctx <$> newForeignPtr p'libusb_exit ctxPtr

When p'libusb_exit is called the segmentation fault occurs.

Note that the error disappears when I change that last line with:

ctx <$> Foreign.Concurrent.newForeignPtr ctxPtr (c'libusb_exit ctxPtr)

I previously got the exact same segmentation fault when calling other bindings-libusb functions. This was caused by using the wrong calling convention on Windows. I used ccall but had to use stdcall. So I assume the current segmentation fault has something to do with the calling convention of FunPtrs.

Any ideas what is causing this? I would really like to solve it. It's the last step before releasing usb-1.0.

Thanks!

Change History (7)

comment:1 Changed 4 years ago by igloo

  • Milestone set to 7.4.1

comment:2 Changed 3 years ago by igloo

  • Milestone changed from 7.4.1 to 7.6.1
  • Priority changed from normal to low

comment:3 Changed 3 years ago by basvandijk

Before I forget, I did some more investigating in this thread.

comment:4 Changed 3 years ago by basvandijk

Summarizing: executing the following isolated program with the argument "fp" (for ForeignPtr) gives an error and without it I get no error:

{-# LANGUAGE ForeignFunctionInterface #-}

module Main where

import Foreign
import Foreign.C.Types
import Control.Concurrent
import System.Environment

main :: IO ()
main = do
  ctxPtr <- alloca $ \ctxPtrPtr -> do
              _ <- c'libusb_init ctxPtrPtr
              peek ctxPtrPtr

  args <- getArgs
  case args of
    ["fp"] -> do
      fp <- newForeignPtr p'libusb_exit ctxPtr
      threadDelay 1000000
      print $ fp == fp

    _ -> c'libusb_exit ctxPtr

data C'libusb_context = C'libusb_context

foreign import stdcall "libusb_init" c'libusb_init
  :: Ptr (Ptr C'libusb_context) -> IO CInt

foreign import stdcall "&libusb_exit" p'libusb_exit
  :: FunPtr (Ptr C'libusb_context -> IO ())

foreign import stdcall "libusb_exit" c'libusb_exit
  :: Ptr C'libusb_context -> IO ()

Note that building the program with the new win64_alpha1 GHC doesn't produce the error. I only get warnings that the stdcall calling convention is not supported. So I guess it then falls back to the ccall calling convention which does work.

Make sure to give these flags to cabal when building the program:

  • --extra-include-dirs="...\libusb\include\libusb-1.0"
  • --extra-lib-dirs="...\libusb\MinGW32\dll" or: --extra-lib-dirs="...\libusb\MinGW64\dll" when building with the new win64_alpha1 GHC.

and make sure the libusb-1.0.dll is in your working directory when running the program.

comment:5 Changed 3 years ago by simonmar

  • difficulty set to Unknown
  • Owner set to simonmar
  • Priority changed from low to high

The FunPtr that you pass to mkForeignPtr must use the ccall calling convention. This should be documented - I'll fix that.

comment:6 Changed 3 years ago by basvandijk

Thanks Simon!

Maurício: I guess the best way to solve this in bindings-DSL is to fix the calling convention for FunPtrs to ccall (regardless of BINDINGS_STDCALLCONV).

comment:7 Changed 3 years ago by simonmar

  • Resolution set to fixed
  • Status changed from new to closed

Fixed:

commit d7332cf3731be55edb4e3ad7e8b272a210a8d210
Author: Simon Marlow <[email protected]>
Date:   Thu Aug 2 11:40:17 2012 +0100

    Document that a FinalizerPtr is a pointer to a ccall function (#5254)
    
    It can't be any other calling convention, e.g. stdcall.
Note: See TracTickets for help on using tickets.