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

Support for __dso_handle #114

Open
craig65535 opened this issue Mar 23, 2023 · 9 comments
Open

Support for __dso_handle #114

craig65535 opened this issue Mar 23, 2023 · 9 comments
Labels
enhancement New feature or request

Comments

@craig65535
Copy link

craig65535 commented Mar 23, 2023

Hello,
I thought I would try using purego to interface with macOS's unified logging framework. However I've hit a snag.

The unified logger uses a set of macros like os_log_debug that ultimately call _os_log_internal. This is exported by libSystem.B.dylib - so far so good. However, the first argument to that function is &__dso_handle, which is defined in C headers as

extern struct mach_header __dso_handle;

I'm not sure how to resolve this symbol. It's not exported by libSystem.B.dylib, but rather it's specific to the running executable. I don't understand this fully, but I've read that it's actually dynamically generated by the linker.

Is there a way to reference this symbol with purego, or is this not possible?

@craig65535 craig65535 changed the title Support for global variables? Support for __dso_handle Mar 23, 2023
@TotallyGamerJet
Copy link
Collaborator

Isn't it possible to just use Dlsym(lib, "__dso_handle")?

Note that it returns a pointer to the value.

@craig65535
Copy link
Author

craig65535 commented Mar 24, 2023

That returns an error (symbol not found). It's not in libSystem.B.dylib. As I said I'm not 100% familiar with this, but I think it's something the linker generates - a dynamic executable would have its own __dso_handle.

@TotallyGamerJet
Copy link
Collaborator

You are correct that it's unique to each dynamic object according to this StackOverflow post. I'm not sure how to reference it in Go. I am not familiar with this API either so more research needs to be done

@craig65535
Copy link
Author

Something that would help here is the ability to lookup a given symbol/C-style global variable in the current process. But I don't know how feasible that is without full cgo.

@TotallyGamerJet
Copy link
Collaborator

How would you do this with Cgo?

@craig65535
Copy link
Author

unsafe.Pointer(&C.__dso_handle)

@TotallyGamerJet
Copy link
Collaborator

So, cgo outputs Go code that links to the C symbol.

// Code generated by cmd/cgo; DO NOT EDIT.

//line /Users/jarrettkuklis/Documents/GolandProjects/purego/test/main.go:1:1
package main

//#include <os/log.h>
import _ "unsafe"
import (
	"fmt"
	"unsafe"
)

func main() {
	fmt.Printf("%x\n", unsafe.Pointer(&( /*line :11:37*/*_Cvar___dso_handle /*line :11:50*/)))
}

// --- _cgo_types.go ---
type _Ctype_struct_mach_header struct {
	magic		_Ctype_uint32_t
	cputype		_Ctype_int32_t
	cpusubtype	_Ctype_int32_t
	filetype	_Ctype_uint32_t
	ncmds		_Ctype_uint32_t
	sizeofcmds	_Ctype_uint32_t
	flags		_Ctype_uint32_t
}
//go:linkname __cgo___dso_handle __dso_handle
//go:cgo_import_static __dso_handle
var __cgo___dso_handle byte
var _Cvar___dso_handle *_Ctype_struct_mach_header = (*_Ctype_struct_mach_header)(unsafe.Pointer(&__cgo___dso_handle))

If you go ahead and copy this into a plan Go code you'll get something like this.

package main

import (
	"fmt"
	"unsafe"
)

type mach_header struct {
	magic      uint32
	cputype    int32
	cpusubtype int32
	filetype   uint32
	ncmds      uint32
	sizeofcmds uint32
	flags      uint32
}

//go:linkname __cgo___dso_handle __dso_handle
//go:cgo_import_static __dso_handle
var __cgo___dso_handle byte
var _Cvar___dso_handle *mach_header = (*mach_header)(unsafe.Pointer(&__cgo___dso_handle))

func main() {
	fmt.Printf("%x\n", unsafe.Pointer(_Cvar___dso_handle))
}

Although this doesn't compile because the //go:cgo_import_static is only available in Cgo. The cgo docs state that it's only used for -linkmode=external. We want the Go linker so we set it to internal and replace the comment with //go:cgo_import_dynamic but the single symbol version of this is not accessible either without cgo. So then I replaced it with the three argument version (//go:cgo_import_dynamic __dso_handle __dso_handle "").

Now it finally compiles but points to some really low address (0x680000) when Cgo version points to a higher address (0x104280000). And when you try to dereference the go version it will SIGSEGV. I think this is an issue with how //go:cgo_import_dynamic is implemented because on amd64 it doesn't work at all _Cvar___dso_handle points to nil.

@TotallyGamerJet
Copy link
Collaborator

@craig65535

I have had some time to look into this. The reason trying to load it with purego doesn't work is because _dso_handle symbol is add by the macOS C linker and Go's linker doesn't add it.

However, the data for this symbol is still able to be obtained since it is just the information about the currently running macho file. This information can be grabbed using just the stdlib:

package main

import (
	"debug/macho"
	"fmt"
	"log"
	"os"
	"structs"
)

// https://opensource.apple.com/source/xnu/xnu-2050.18.24/EXTERNAL_HEADERS/mach-o/loader.h
type Mach_header struct {
	_          structs.HostLayout // ensures it matches system layout
	Magic      uint32
	Cputype    int32
	Cpusubtype int32
	Filetype   uint32
	Ncmds      uint32
	Sizeofcmds uint32
	Flags      uint32
	_          uint32 // reserved
}

func main() {
	dir, err := os.Executable()
	if err != nil {
		log.Fatal(err)
	}
	ex, err := macho.Open(dir) // or OpenFat if it is a fat macho
	if err != nil {
		log.Fatal(err)
	}
	header := Mach_header{
		Magic:      ex.FileHeader.Magic,
		Cputype:    int32(ex.FileHeader.Cpu),
		Cpusubtype: int32(ex.FileHeader.SubCpu),
		Filetype:   uint32(ex.FileHeader.Type),
		Ncmds:      ex.FileHeader.Ncmd,
		Sizeofcmds: ex.FileHeader.Cmdsz,
		Flags:      ex.FileHeader.Flags,
	}
	fmt.Println(header)
}

I checked and this results in the exact same data as the _dso_handle symbol. You can then use &header as the argument to _os_log_internal. I believe this solves your issue. If so, please close this issue. Thank you.

@craig65535
Copy link
Author

@TotallyGamerJet I missed your update at first - thank you! I will try this and reply back here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

3 participants