treeform / puppy

Puppy fetches via HTTP and HTTPS
MIT License
184 stars 27 forks source link

Fetch hangs forever when used within a Windows DLL #76

Closed Invoke-Mimikatz closed 1 year ago

Invoke-Mimikatz commented 1 year ago

Hi,

I am trying to make an application with a DLL component that uses Puppy to fetch resources from the web. I've run into a bug in my code related to the puppy.fetch() function. This problem only seems to happen when the fetch() function is used within code compiled as a DLL. When code execution hits a call to fetch(), instead of succeeding or timing out, fetch() seems to hang up program execution.

A small bit of code I am using to demonstrate the problem:

# Compile with:
# nim -d:dll -d:mingw --cpu:amd64 --nomain --app:lib c dll_test.nim
#
# Run with:
# rundll32.exe dll_test.dll,DllMain
#
#
# Alternately, compile as an EXE
# nim -d:mingw --cpu:amd64 c dll_test.nim
#
# Run this one as an executable:
# dll_test.exe

import winim/lean
import puppy

var started = false

proc fetchStuff*() =
  ## Simply runs some functionality
  MessageBox(0, "Start", "Start", 0)
  let res = fetch("https://google.com") # <--- Code execution hangs up HERE (only when compiled as a DLL)
  MessageBox(0, "Finish", "Finish", 0)

when(defined(dll)):
  proc NimMain() {.cdecl, importc.}
    ## Import NimMain -- we need to call this before other code is executed

  proc DllMain(hinstDLL: HINSTANCE, fdwReason: DWORD, lpvReserved: LPVOID): BOOL {.stdcall, exportc, dynlib.} =
    ## Exported DllMain function. Execution starts here for a DLL
    NimMain()
    if(not started):
      started = true # Just so we only start this once...
      fetchStuff()

    return true

else: # EXE version of this code
  fetchStuff()

Expected results of this code:

Actual results (when running as a DLL):

Since I thought this might be a problem with the Windows rundll32.exe binary, I wrote a small Nim DLL loader

# Compile with `nim c rundll.nim`
when(defined(windows)):
  const libname = r".\dll_test.dll"

# From the imported DLL, load the fetchStuff() function
proc fetchStuff() {.importc, dynlib: libname.}

# Then run the imported function
fetchStuff()

# Use the following command to run the dll:
# rundll.exe

This also has the same results and hangs up execution, showing only the message box before the call to fetch(), but not the following MessageBox()

guzba commented 1 year ago

Hello, the issue here is running fetch in DllMain (DLL_PROCESS_ATTACH). There is a rule that you should not talk to any other DLLs during that procedure. In this case, calling fetch makes Puppy talk to the Windows HTTP DLL API. For more info on this see https://docs.microsoft.com/en-us/windows/win32/dlls/dynamic-link-library-best-practices?redirectedfrom=MSDN#general_best_practices

Thanks for providing a clear and excellent bug report. It made it very straightforward to identify the issue.