{"id":13600265,"url":"https://github.com/notti/nocgo","last_synced_at":"2026-01-23T05:04:08.195Z","repository":{"id":57485091,"uuid":"169705818","full_name":"notti/nocgo","owner":"notti","description":"dlopen in go without cgo","archived":false,"fork":false,"pushed_at":"2019-06-19T20:12:44.000Z","size":1146,"stargazers_count":204,"open_issues_count":3,"forks_count":13,"subscribers_count":6,"default_branch":"master","last_synced_at":"2025-08-13T20:43:06.126Z","etag":null,"topics":["cgo","dlopen","golang"],"latest_commit_sha":null,"homepage":null,"language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/notti.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2019-02-08T08:38:44.000Z","updated_at":"2025-08-12T12:25:14.000Z","dependencies_parsed_at":"2022-08-26T11:10:57.437Z","dependency_job_id":null,"html_url":"https://github.com/notti/nocgo","commit_stats":null,"previous_names":[],"tags_count":2,"template":false,"template_full_name":null,"purl":"pkg:github/notti/nocgo","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/notti%2Fnocgo","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/notti%2Fnocgo/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/notti%2Fnocgo/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/notti%2Fnocgo/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/notti","download_url":"https://codeload.github.com/notti/nocgo/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/notti%2Fnocgo/sbom","scorecard":{"id":695440,"data":{"date":"2025-08-11","repo":{"name":"github.com/notti/nocgo","commit":"fc443047424c2d19c5aa878b52b076b6ea0d86eb"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":2.5,"checks":[{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"Token-Permissions","score":-1,"reason":"No tokens found","details":null,"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"Code-Review","score":0,"reason":"Found 0/30 approved changesets -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Maintained","score":0,"reason":"0 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"Dangerous-Workflow","score":-1,"reason":"no workflows found","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"SAST","score":0,"reason":"no SAST tool detected","details":["Warn: no pull requests merged into dev branch"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}},{"name":"Binary-Artifacts","score":9,"reason":"binaries present in source code","details":["Warn: binary detected: steps/2_go/main:1"],"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Pinned-Dependencies","score":-1,"reason":"no dependencies found","details":null,"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"Vulnerabilities","score":10,"reason":"0 existing vulnerabilities detected","details":null,"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"License","score":0,"reason":"license file not detected","details":["Warn: project does not have a license file"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"Branch-Protection","score":0,"reason":"branch protection not enabled on development/release branches","details":["Warn: branch protection not enabled for branch 'master'"],"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}}]},"last_synced_at":"2025-08-22T03:31:42.928Z","repository_id":57485091,"created_at":"2025-08-22T03:31:42.928Z","updated_at":"2025-08-22T03:31:42.928Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28680623,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-23T04:33:33.518Z","status":"ssl_error","status_checked_at":"2026-01-23T04:33:30.433Z","response_time":59,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["cgo","dlopen","golang"],"created_at":"2024-08-01T18:00:33.745Z","updated_at":"2026-01-23T05:04:08.159Z","avatar_url":"https://github.com/notti.png","language":"Go","funding_links":[],"categories":["Go"],"sub_categories":[],"readme":"nocgo\n=====\n\nTested on go1.11 and go1.12.\n\n[![GoDoc](https://godoc.org/github.com/notti/nocgo?status.svg)](https://godoc.org/github.com/notti/nocgo)\n\nThis repository/package contains a *proof of concept* for calling into C code *without* using cgo.\n\n\u003e **WARNING!** This is meant as a proof of concept and subject to changes.\nFurthermore this is highly experimental code. DO NOT USE IN PRODUCTION.\nThis could cause lots of issues from random crashes (there are tests - but there is definitely stuff that's not tested) to teaching your gopher to talk [C gibberish](https://cdecl.org/).\n\n\u003e **WARNING** nocgo supports both cgo and missing cgo as environment. So if you want to ensure cgo not being used don't forget `CGO_ENABLED=0` as environment variable to `go build`.\n\nTodo\n----\n\n- Callbacks into go\n- Structures\n\nWhen that's done write up a proposal for golang inclusion.\n\nUsage\n-----\n\nLibraries can be loaded and unloaded similar to `dlopen` and `dlclose`, but acquiring symbols (i.e., functions, global variables) is a bit different, since a function specification (i.e., arguments, types, return type) is also needed. Furthermore, C-types must be translated to go-types and vice versa.\n\nThis works by providing a function specification as a pointer to a function variable. A call to `lib.Func` will examine arguments and eventual return value (only one or no return values allowed!), and set the function variable to a wrapper that will call into the desired C-function.\n\n### Type Mappings\n\nGo types will be mapped to C-types according to the following table:\n\nGo type                                       | C Type\n--------------------------------------------- | ------\n`int8`, `byte`                                | `char`\n`uint8`, `bool`                               | `unsigned char`\n`int16`                                       | `short`\n`uint16`                                      | `unsigned short`\n`int32`                                       | `int`\n`uint32`                                      | `unsigned int`\n`int64`                                       | `long`\n`uint64`                                      | `unsigned long`\n`float32`                                     | `float`\n`float64`                                     | `double`\n`[]`, `uintptr`, `reflect.UnsafePointer`, `*` | `*`\n\nThe last line means that slices and pointers are mapped to pointers in C. Pointers to structs are possible.\n\nPassing `struct`, `complex`, and callback functions is not (yet) supported.\n\n\u003e **WARNING** `struct`s that are referenced **must** follow C alignment rules! There is **no** type checking, since this is actually not possible due to libraries not knowing their types...\n\nGo `int` was deliberately left out to avoid confusion, since it has different sizes on different architectures.\n\n### Example\n\nAn example using `pcap_open_live` from libpcap (C-definition: `pcap_t *pcap_open_live(const char *device, int snaplen, int promisc, int to_ms, char *errbuf)\n`) could look like the following example:\n\n```golang\n\n// Load the library\nlib, err := nocgo.Open(\"libpcap.so\")\nif err != nil {\n    log.Fatalln(\"Couldn't load libpcap: \", err)\n}\n\n// func specification\nvar pcapOpenLive func(device []byte, snaplen int32, promisc int32, toMS int32, errbuf []byte) uintptr\n// Get a handle for the function\nif err := lib.Func(\"pcap_open_live\", \u0026pcapOpenLive); err != nil {\n    log.Fatalln(\"Couldn't get pcap_open_live: \", err)\n}\n\n// Do the function call\nerrbuf := make([]byte, 512)\npcapHandle := pcapOpenLive(nocgo.MakeCString(\"lo\"), 1500, 1, 100, errbuf)\n\n// Check return value\nif pcapHandle == 0 {\n    log.Fatalf(\"Couldn't open %s: %s\\n\", \"lo\", nocgo.MakeGoStringFromSlice(errbuf))\n}\n\n// pcapHandle can now be used as argument to the other libpcap functions\n```\n\nA full example is contained in [examplelibpcap](examplelibpcap) and another one in [example](example).\n\n\u003e **WARNING** nocgo supports both cgo and missing cgo as environment. So if you want to ensure cgo not being used don't forget `CGO_ENABLED=0` as environment variable to `go build`.\n\nSupported Systems\n-----------------\n\n* linux with glibc\n* FreeBSD\u003cbr\u003e\n  *Errata:* FreeBSD requires the exported symbols `_environ` and `_progname`. This is only possible inside cgo or stdlib. So for building on FreeBSD, `-gcflags=github.com/notti/nocgo/fakecgo=-std` is required (This doesn't seem to work for `go test` - so examples work, but test does not)).\n\nWith some small modifications probably all systems providing `dlopen` can be supported. Have a look at [dlopen_OS.go](dlopen_linux.go) and [symbols_OS.go](fakecgo/symbols_linux.go) in fakecgo.\n\nSupported Architectures\n-----------------------\n\n* 386\n* amd64\n\nImplementing further architectures requires\n* Building trampolines for [fakecgo](fakecgo) (see below)\n* Implementing the cdecl callspec in [call_.go](call_amd64.go)/[.s](call_amd64.s)\n\nHow does this work\n------------------\n\n### nocgo\n\nnocgo imports `dlopen`, `dlclose`, `dlerror`, `dlsym` via `go:cgo_import_dynamic` in [dlopen_OS.go](dlopen_linux.go). `lib.Func` builds a specification on where to put which argument in [call_arch.go](call_amd64.go). go calls such a function by dereferencing, where it points to, provide this address in a register and call the first address that is stored there. nocgo uses this mechanism by putting a struct there, that contains the address to a wrapper followed by a pointer to the what `dlsym` provided and a calling specification. The provided wrapper uses `cgocall` from the runtime to call an assembly function and pass the spec and a pointer to the arguments to it. This assembly function is implemented in call_arch.s and it uses the specification to place the arguments into the right places, calls the pointer provided by `dlsym` and then puts the return argument into the right place if needed.\n\nThis is basically what `libffi` does. So far cdecl for 386 (pass arguments on the stack in right to left order, return values are in AX/CX or ST0) and amd64 (pass arguments in registers DI, SI, DX, CX, R8, R9/X0-X7 and the stack in right to left order, number of floats in AX, fixup alignment of stack) are implemented.\n\nSo far so simple. `cgocall` could actually be used to call a C function directly - but it is only capable of providing one argument!\n\nBut there is a second issue. For simple C functions we could leave it at that (well we would need to use `asmcgocall`, because `cgocall` checks, if cgo is actually there...). But there is this thing called Thread Local Storage (TLS) that is not too happy about golang not setting that up correctly. This is already needed if you do `printf(\"%f\", 1)` with glibc!\n\nSo we need to provide some functionality that cgo normally provides, which is implemented in fakecgo:\n\n### fakecgo\n\ngo sets up it's own TLS during startup in runtime/asm_arch.s in `runtime·rt0_go`. We can easily prevent that by providing setting the global variable `_cgo_init` to something non-zero (easily achieved with `go:linkname` and setting a value). But this would crash go, since if this is the case, go actually calls the address inside this variable (well ok we can provide an empty function).\n\nAdditionally, this would provide correct TLS only on the main thread. This works until one does a lot more than just call one function, so we need to fixup also some other stuff.\n\nSo next step: set `runtime.is_cgo` to true (again - linkname to the rescue). But this will panic since now the runtime expects the global variables `_cgo_thread_start`, `_cgo_notify_runtime_init_done`, `_cgo_setenv`, and `_cgo_unsetenv` to point to something. Ok so let's just implement those.\n\n* `_cgo_notify_runtime_init_done` is easy - we don't need this one: empty function.\n* `_cgo_setenv` is also simple: just one function call to `setenv`\n* `_cgo_unsetenv` is the same.\n* `_cgo_init` queries the needed stack size to update g-\u003estack so that runtime stack checks do the right thing (it also provides a setg function we come to that later...)\n* `_cgo_thread_start` is a bit more involved... It starts up a new thread with `pthread_create` and does a bit of setup.\n\nSo this should be doable - right?\n\nWell easier said than done - those are implemented in C-code in runtime/cgo/*c presenting some kind of chicken and egg problem to us.\n\nSo I started out with reimplementing those in go assembly (remember: we want to be cgo free) which is available in the tag asm. Since this is really cumbersome and needs a lot of code duplication, I experimented a bit if we can do better.\n\nAaaand we can:\n\n[fakecgo/trampoline_arch.s](fakecgo/trampoline_amd64.s) contains the above mentioned entry points, and \"converts\" the C-calling conventions to go calling conventions (e.g. move register passed arguments to the stack). Then it calls the go functions in [fakecgo/cgo.go](fakecgo/cgo.go).\n\nOk - but we still need all those pthread and C-library-functions. Well we can import the symbols (like with `dlopen`). So all we need is a way to call those:\n\nThe trampoline file also contains an `asmlibccall6` function that can call C-functions with a maximum of 6 integer arguments and one return value. [fakecgo/libccall.go](fakecgo/libccall.go) maps this onto more convenient go functions with 1-6 arguments and [fakecgo/libcdefs.go](fakecgo/libcdefs.go) further maps those into nice functions that look like the C functions (e.g. `func pthread_create(thread *pthread_t, attr *pthread_attr, start, arg unsafe.Pointer) int32`). Well this was not exactly my idea - the runtime already does that for solaris and darwin (runtime/os_solaris.go, runtime/syscall_solaris.go, runtime/sys_solaris_amd64.s) - although my implementation here is kept a bit simpler since it only ever will be called from gocode pretending to be C.\n\nSo now we can implement all the above mentioned cgo functions in pure (but sometimes a bit ugly) go in [fakecgo/cgo.go](fakecgo/cgo.go). Ugly, because those functions are called with lots of functionality missing! Writebarriers are **not** allowed, as are stack splits.\n\nThe upside is, that the only arch dependent stuff are the trampolines (in assembly) and the only OS dependent stuff are the symbol imports.\n\nExcept for freebsd (which needs two exported symbols, as mentioned above) all those things work outside the runtime and no special treatment is needed. Just import fakecgo and all the cgo setup just works (except if you use cgo at the same time - then the linker will complain).\n\nBenchmarks\n----------\n\nThis will be a bit slower than cgo. Most of this is caused by argument rearranging:\n\n### 386\n\n```\nname           old time/op    new time/op    delta\nEmpty-4          84.5ns ± 0%    86.4ns ± 2%    +2.22%  (p=0.000 n=8+8)\nFloat2-4         87.9ns ± 1%   222.5ns ± 6%  +153.20%  (p=0.000 n=8+10)\nStackSpill3-4     116ns ± 1%     130ns ± 1%   +12.04%  (p=0.000 n=8+8)\n```\n\nFloat is so slow since that type is at the end of the comparison chain.\n\n### amd64\n\n```\nname           old time/op    new time/op    delta\nEmpty-4          76.8ns ±10%    80.1ns ± 9%   +4.24%  (p=0.041 n=10+10)\nFloat2-4         78.4ns ± 5%    81.4ns ± 9%   +3.80%  (p=0.033 n=9+10)\nStackSpill3-4    96.2ns ± 5%   120.7ns ± 7%  +25.46%  (p=0.000 n=10+9)\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnotti%2Fnocgo","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fnotti%2Fnocgo","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnotti%2Fnocgo/lists"}