Golang中共享库的Goroutine无法启动问题

Golang中共享库的Goroutine无法启动问题 我正在尝试创建一个用于SSH授权的PAM模块。

不幸的是,在SSH流程中使用时,每个goroutine在PAM模块内都无法启动。而使用pamtester时,它确实可以正常工作,没有任何问题。

以下是代码:

package main

/*
#include <security/pam_modules.h>
typedef const char cchar_t;
*/
import "C"
import (
	"log"
	"time"
)

func main() {}

//export pam_sm_authenticate
func pam_sm_authenticate(pamh *C.pam_handle_t, flags C.int, argc C.int, argv **C.cchar_t) C.int {
	log.Printf("golib.so: before start goroutine")
	go func() {
        // This will not happen and the whole program will hang from
        // here on...
		log.Printf("golib.so: something out of the goroutine")
	}()
	log.Printf("golib.so: after start goroutine")

	return C.PAM_SUCCESS
}

//export pam_sm_setcred
func pam_sm_setcred(pamh *C.pam_handle_t, flags C.int, argc C.int, argv **C.cchar_t) C.int {
	return C.PAM_IGNORE
}

环境

  • OS: Ubuntu 22.04.4 LTS
  • SSH: OpenSSH_8.9p1 Ubuntu-3ubuntu0.10, OpenSSL 3.0.2 15 Mar 2022
  • Go: go version go1.22.5 linux/amd64
  • pamtester: 0.1.2

类似问题

  1. golang/go#57394
  2. golang/go#15538
  3. golang/go#15556

到目前为止,这些工单都没有帮助我解决问题。

我的主要问题甚至不在于我打算使用goroutine,而是如果我使用http.Client,goroutine无处不在。

顺便说一句:不要过多关注方法内部发生了什么。goroutine甚至没有启动。甚至连golib.so: goroutine begin都不会显示。

所以:我很乐意接受建议,让goroutine在SSH内的PAM模块上下文中工作,或者在不使用goroutine的情况下进行HTTP请求。smirk

感谢您的支持!


更多关于Golang中共享库的Goroutine无法启动问题的实战教程也可以访问 https://www.itying.com/category-94-b0.html

2 回复

你好,可以尝试以下方法:

  • 使用同步的 HTTP 调用,而不使用 goroutine。
  • 创建一个自定义的 http.Transport 来禁用 goroutine。
  • 检查 SSH/PAM 日志以排查线程问题。
  • 使用 net.Dial 进行手动的 HTTP 请求。
  • 使用 runtime.GOMAXPROCS(1) 来限制 Go 线程数。

希望这些方法对你有用……

更多关于Golang中共享库的Goroutine无法启动问题的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


这是一个典型的Go共享库在C环境中运行时的问题。问题的核心在于Go的运行时初始化与PAM模块加载机制不兼容。当SSH加载PAM模块时,Go的运行时没有正确初始化,导致goroutine无法启动。

以下是解决方案:

package main

/*
#include <security/pam_modules.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
typedef const char cchar_t;
*/
import "C"
import (
	"log"
	"os"
	"runtime"
	"syscall"
	"unsafe"
)

func main() {}

// 初始化Go运行时
func init() {
	// 确保Go运行时正确初始化
	runtime.LockOSThread()
}

//export pam_sm_authenticate
func pam_sm_authenticate(pamh *C.pam_handle_t, flags C.int, argc C.int, argv **C.cchar_t) C.int {
	log.Printf("golib.so: before start goroutine")
	
	// 使用fork+exec模式来启动goroutine
	pid, err := syscall.ForkExec("/proc/self/exe", []string{"goroutine_worker"}, &syscall.ProcAttr{
		Env:   os.Environ(),
		Files: []uintptr{uintptr(syscall.Stdin), uintptr(syscall.Stdout), uintptr(syscall.Stderr)},
	})
	
	if err != nil {
		log.Printf("golib.so: fork failed: %v", err)
		return C.PAM_SUCCESS
	}
	
	// 非阻塞等待
	go func() {
		var status syscall.WaitStatus
		syscall.Wait4(pid, &status, 0, nil)
		log.Printf("golib.so: goroutine worker exited")
	}()
	
	log.Printf("golib.so: after start goroutine")
	return C.PAM_SUCCESS
}

// 替代方案:使用C线程启动goroutine
//export start_goroutine_via_c_thread
func start_goroutine_via_c_thread() {
	// 通过CGO调用pthread_create来创建线程
	C.pthread_create(nil, nil, (*[0]byte)(C.start_go_routine), nil)
}

//export go_routine_entry
func go_routine_entry() {
	log.Printf("golib.so: goroutine started via C thread")
	// 这里可以执行你的HTTP请求
}

// 如果必须使用HTTP客户端,使用同步方式
func makeHTTPRequestSync() {
	// 使用net/http包但避免goroutine
	// 注意:这仍然可能内部使用goroutine,所以最好使用fork模式
}

//export pam_sm_setcred
func pam_sm_setcred(pamh *C.pam_handle_t, flags C.int, argc C.int, argv **C.cchar_t) C.int {
	return C.PAM_IGNORE
}

需要添加的C代码:

#include <pthread.h>

extern void go_routine_entry();

void* start_go_routine(void* arg) {
    go_routine_entry();
    return NULL;
}

编译命令:

# 使用CGO_ENABLED=1和-buildmode=c-shared
go build -buildmode=c-shared -o pam_golang.so ./pam_module.go

关键点:

  1. Go共享库在PAM环境中运行时,运行时初始化不完整
  2. 使用fork+exec模式是绕过此限制的最可靠方法
  3. 或者通过C线程(pthread)间接启动goroutine
  4. 避免在PAM模块中直接使用可能创建goroutine的Go库(如net/http)

对于HTTP请求,建议使用外部进程或守护进程来处理,PAM模块只进行认证决策。

回到顶部