Help understanding mapping Go<->C structures across IOCTL's

已查看 343 次
跳至第一个未读帖子

George Hartzell

未读,
2019年8月29日 20:15:202019/8/29
收件人 GoLang Nuts

Quick summary, I'm trying to understand the Go structures that cgo
gives me, how they map back onto the C structures and why a Go struct
that doesn't quite match the cgo output seems to work anyway.

I've called out two explicit questions down below.

---

The Linux kernel provides a character device interface to I2C devices,
and the [i2c-tools] package provides a C library with "convenient"
access functions.

I'm working on a Go library for the kernel interface on a Raspberry
Pi, because I need it for a project *and* as an etude [so please,
don't do too much of my "homework" for me...;)]

While digging around for prior art, I found a [partial implementation
by tetsu-koba][tetsu-koba]. Starting from that, I have working bits
to talk to devices via the I2C versions of the commands.

My next step was to implement the [i2c-tools] SMBus functions. That
requires Go versions of the two C structures that they use. A bit of
research into creating the mapping led to using `cgo -godefs`. Along
the way, I stumbled on a [partial implementation of the SMBus
bits][brandonagr-smbus] by brand...@gmail.com.

I ended up with this cgo input file:

```go
package main

/*
#include <stdio.h>
#include <linux/i2c.h>
#include <linux/i2c-dev.h>
*/
import "C"

type I2CMsg C.struct_i2c_msg

const Sizeof_I2CMsg = C.sizeof_struct_i2c_msg

type I2CSMBusIoctlData C.struct_i2c_smbus_ioctl_data

const Sizeof_I2CSMBusIoctlData = C.sizeof_struct_i2c_smbus_ioctl_data

type Blort C.struct_blort

type I2CSMBusData C.union_i2c_smbus_data

const Sizeof_I2CSMBusData = C.sizeof_union_i2c_smbus_data
```

It generates this output:

```go
// Code generated by cmd/cgo -godefs; DO NOT EDIT.
// cgo -godefs foo.go

package main

type I2CMsg struct {
Addr uint16
Flags uint16
Len uint16
Buf *uint8
}

const Sizeof_I2CMsg = 0xc

type I2CSMBusIoctlData struct {
Write uint8
Command uint8
Size uint32
Data *[34]byte
}

const Sizeof_I2CSMBusIoctlData = 0xc

type Blort struct{}

type I2CSMBusData [34]byte

const Sizeof_I2CSMBusData = 0x22
```

@brandonagr ended up with something that's different for his version
of the i2c_smbus_ioctl_data struct:

```go
type i2c_smbus_ioctl_data struct {
read_write uint8
command uint8
size int
data *uint8
}
```

but I can see how it's the same "shape".

## Question #1

On the *other hand, @tetsu-koba's version of the i2c_msg struct:

```go
type i2c_msg struct {
addr uint16
flags uint16
len uint16
__padding uint16
buf uintptr
}
```

seems to be fundamentally different from the `cgo -godefs` version:

```go
type I2CMsg struct {
Addr uint16
Flags uint16
Len uint16
Buf *uint8
}
```

Before I figured out how to use `cgo -godefs`, I rationalized
@tetsu-koba's struct with the explanation that the `buf` pointer was
being padded out to a 32-bit boundary and was comforted by the fact
that it seems to work (I can use it to talk to e.g. an MCP9808
temperature sensor).

But, the `cgo -godefs` output seems to be a different shape, I don't
see how the Buf field ends up in the right place. I haven't tried
*using* it yet, I'd like to understand the why before I comfort myself
with a "seems to work, onward...". Left to my own devices I thought
about trying to get at the assembly language output for the various
structs and seeing what was going on, but that seemed to be One Yak
Too Far.

Can anyone explain [or a big big clue] what's going on? Is one right
and the other wrong? Are both somehow correct?

## Question #2

While I'm on this topic, I notice that `cgo -godefs` converted the
struct/field names to Go camel case versions (and stripped the "read_"
prefix from one). But, both @tetsu-koba and @brandonagr stuck with
the C struct names. I've noticed that various go libaries that refer
to kernel data structures (e.g. constants/#defines) don't rename them
in Go style.

What is the accepted practice in this situation?

Thanks!

g.

[brandonagr-smbus]: https://groups.google.com/d/msg/golang-nuts/zHyqv36oFVI/Jzx1ztCZbHwJ
[i2c-tools]: https://git.kernel.org/pub/scm/utils/i2c-tools/i2c-tools.git/
[tetsu-koba]: https://gist.github.com/tetsu-koba/33b339d26ac9c730fb09773acf39eac5

Ian Lance Taylor

未读,
2019年8月29日 20:30:342019/8/29
收件人 hart...@alerce.com、GoLang Nuts
This is not a helpful answer but I want to say that using "cgo
-godefs" in this way is not supported. You're welcome to use it but
it may well break in the future. The "-godefs" option is specifically
for generating files used in the syscall and golang.org/x/sys
packages, it's not intended for general use. We instead recommend
using import "C" and using cgo in the usual way as described at
https://golang.org/cmd/cgo/ and https://blog.golang.org/c-go-cgo .

Ian

George Hartzell

未读,
2019年8月29日 21:26:262019/8/29
收件人 Ian Lance Taylor、hart...@alerce.com、GoLang Nuts
Ian Lance Taylor writes:
> On Thu, Aug 29, 2019 at 5:15 PM George Hartzell <hart...@alerce.com> wrote:
> >
> > Quick summary, I'm trying to understand the Go structures that cgo
> > gives me, how they map back onto the C structures and why a Go struct
> > that doesn't quite match the cgo output seems to work anyway.
> >
> > I've called out two explicit questions down below.
> >
> > ---
> >
> > The Linux kernel provides a character device interface to I2C devices,
> > and the [i2c-tools] package provides a C library with "convenient"
> > access functions.
> >
> > I'm working on a Go library for the kernel interface on a Raspberry
> > Pi, because I need it for a project *and* as an etude [so please,
> > don't do too much of my "homework" for me...;)]
> >
> > [...]
>
> This is not a helpful answer but I want to say that using "cgo
> -godefs" in this way is not supported. You're welcome to use it but
> it may well break in the future. The "-godefs" option is specifically
> for generating files used in the syscall and golang.org/x/sys
> packages, it's not intended for general use. We instead recommend
> using import "C" and using cgo in the usual way as described at
> https://golang.org/cmd/cgo/ and https://blog.golang.org/c-go-cgo .

@iant, thanks for the quick response. Everything's part of the
lesson, so it's useful.

Andrew's [c-go-cgo] article talks about an interface to a C library,
while I'm trying to wrap a set of IOCTL calls into the kernel w/out
requiring supporting C code.

I suppose I *could* write a cgo library that would wrap the C-based
[i2c-tools] library, but I wanted to do it directly in Go, without
adding the [i2c-tools] dependency.

Or, are you saying that I should create a package that does the
`import "C"` and `import "unsafe"` and then somehow uses "C.Ioctl" (or
something, I haven't dug very far into this line of thought yet...) to
make the call into the kernel. I can see how every time such a thing
was built it would use that Go version's latest/greatest understanding
of how to map the Go and C structures, whereas the approach I'm
copying is static and might become out of date. But, in that case,
why do the things in golang.org/x/sys exist (though they're probably
built for each release and are therefor up-to-date by def'n)?

I thought that what I was doing here was essentially extending the set
of syscalls (the C library in [i2c-tools] is "just" a set of wrappers
around the IOCTL's associated with the Linux i2c character device
interface), so the `cgo -godefs` usage was appropriate.

While starting out on this, I found a series of discussions about
adding various IOCTL calls to the golang.org/x/sys packages and
pushback limiting what actually went in there. I thought that at a
minimum additions needed to prove themselves "stand-alone" before
making it into those packages.

Thanks again for the time and thoughts,

g.

[c-go-cgo]: https://blog.golang.org/c-go-cgo
[i2c-tools]: https://git.kernel.org/pub/scm/utils/i2c-tools/i2c-tools.git/

Ian Lance Taylor

未读,
2019年8月30日 18:44:312019/8/30
收件人 hart...@alerce.com、GoLang Nuts
On Thu, Aug 29, 2019 at 6:26 PM George Hartzell <hart...@alerce.com> wrote:
>
> I suppose I *could* write a cgo library that would wrap the C-based
> [i2c-tools] library, but I wanted to do it directly in Go, without
> adding the [i2c-tools] dependency.
>
> Or, are you saying that I should create a package that does the
> `import "C"` and `import "unsafe"` and then somehow uses "C.Ioctl" (or
> something, I haven't dug very far into this line of thought yet...) to
> make the call into the kernel. I can see how every time such a thing
> was built it would use that Go version's latest/greatest understanding
> of how to map the Go and C structures, whereas the approach I'm
> copying is static and might become out of date. But, in that case,
> why do the things in golang.org/x/sys exist (though they're probably
> built for each release and are therefor up-to-date by def'n)?

There is another approach, which is to use import "C", and then write code like

const IoctlConstant = C.IoctlConstant
type GoIoctlType = C.CIoctlType

and then use those types in code that calls unix.Syscall(SYS_IOCTL,
IoctlConstant, &GoIoctlType{value}).

Ian

George Hartzell

未读,
2019年8月30日 19:57:552019/8/30
收件人 Ian Lance Taylor、hart...@alerce.com、GoLang Nuts
Ian Lance Taylor writes:
> On Thu, Aug 29, 2019 at 6:26 PM George Hartzell <hart...@alerce.com> wrote:
> [...]
> There is another approach, which is to use import "C", and then write code like
>
> const IoctlConstant = C.IoctlConstant
> type GoIoctlType = C.CIoctlType
>
> and then use those types in code that calls unix.Syscall(SYS_IOCTL,
> IoctlConstant, &GoIoctlType{value}).

Hi Ian,

Thank you for the followup^2.

After re-reading all of the things that you'd pointed me at, I'd
started heading down that road a bit, but am stuck on one thing and
have a question.

# Where I'm currently stuck

Here's something that compiles and runs:

```
package i2c

// #include <linux/i2c.h>
// #include <linux/i2c-dev.h>
import "C"
import "fmt"

func Foo() {
im := C.struct_i2c_msg{}
buf := [2]byte{}

im.addr = 0x18
im.flags = C.I2C_M_RD
im.len = C.__u16(len(buf))
// im.buf = &buf[0]
fmt.Println(im)
}
````

The original C structure is defined like so (w/ comments, #defines elided):

```
struct i2c_msg {
__u16 addr; /* slave address */
__u16 flags;
__u16 len; /* msg length */
__u8 *buf; /* pointer to msg data */
};
```

The other approaches that I cribbed supplied the buffer by declaring a
byte slice and taking the address of the first element. If I uncomment
line 15 in the above, I get

```
i2c/i2c.go:15:9: cannot use &buf[0] (type *byte) as type *_Ctype_uchar
in assignment
```

I can't come up with any cast that makes the compiler happy. I'm also
unsure whether that line runs afoul of this commandment: *Go code may
not store a Go pointer in C memory* from https://golang.org/cmd/cgo/.

Perhaps I should be allocating that memory myself with a call to
`C.malloc` or ???

# The question

If I leave out the buf bit and build it, I end with a dynamically
linked executable, e.g.

```
pi@raspberrypi:~/temper/go/cgo-normal-way $ ldd dummy
linux-vdso.so.1 (0x7ef23000)
/usr/lib/arm-linux-gnueabihf/libarmmem-${PLATFORM}.so => /usr/lib/arm-linux-gnueabihf/libarmmem-v7l.so (0x76f14000)
libpthread.so.0 => /lib/arm-linux-gnueabihf/libpthread.so.0 (0x76ed3000)
libc.so.6 => /lib/arm-linux-gnueabihf/libc.so.6 (0x76d85000)
/lib/ld-linux-armhf.so.3 (0x76f29000)
```

While this is an personal homework problem, I'm also heading towards
building an application and one of the advantages of using Go is the
statically compiled binary. After your initial comment, I'd thought
perhaps I'd just end up using some cgo magic for the types and structs
but I'd still end up with a static binary. That doesn't seem to be
the case.

If I go back mimicking the approach used in the x/sys libraries (cgo
-godefs), what particular "private" behavior am I depending on? Is it
the ability to use `cgo -godefs` to figure out a Go equivalent for the
C data structures, or is it counting on the
``uintptr(unsafe.Pointer(...))` to do the right thing, or something
else?

Thanks again (you've plowed through lots of words...),

g.

Ian Lance Taylor

未读,
2019年8月30日 20:26:442019/8/30
收件人 hart...@alerce.com、GoLang Nuts
im.buf = (*C.uchar)(unsafe.Pointer(&buf[0]))


> I'm also
> unsure whether that line runs afoul of this commandment: *Go code may
> not store a Go pointer in C memory* from https://golang.org/cmd/cgo/.

There is no C memory here as yet. That code will be OK if you pass
&im to unix.Syscall. But it will not be OK if you pass &im to
C.ioctl, as that would violate this rule from
https://golang.org/cmd/cgo: "Go code may pass a Go pointer to C
provided the Go memory to which it points does not contain any Go
pointers." In this case the Go pointer &im would contain the Go
pointer &buf[0]. But that rule applies to calling cgo functions; it
does not apply to calling unix.Syscall.


> Perhaps I should be allocating that memory myself with a call to
> `C.malloc` or ???

That would also work, and be safe.


> # The question
>
> If I leave out the buf bit and build it, I end with a dynamically
> linked executable, e.g.
>
> ```
> pi@raspberrypi:~/temper/go/cgo-normal-way $ ldd dummy
> linux-vdso.so.1 (0x7ef23000)
> /usr/lib/arm-linux-gnueabihf/libarmmem-${PLATFORM}.so => /usr/lib/arm-linux-gnueabihf/libarmmem-v7l.so (0x76f14000)
> libpthread.so.0 => /lib/arm-linux-gnueabihf/libpthread.so.0 (0x76ed3000)
> libc.so.6 => /lib/arm-linux-gnueabihf/libc.so.6 (0x76d85000)
> /lib/ld-linux-armhf.so.3 (0x76f29000)
> ```
>
> While this is an personal homework problem, I'm also heading towards
> building an application and one of the advantages of using Go is the
> statically compiled binary. After your initial comment, I'd thought
> perhaps I'd just end up using some cgo magic for the types and structs
> but I'd still end up with a static binary. That doesn't seem to be
> the case.

OK, that is true. Since your program will be using cgo, even though
you aren't calling any C functions, it will be dynamically linked by
default. You could try using "go build -ldflags=-extldflags=-static".
I don't know whether it will work.


> If I go back mimicking the approach used in the x/sys libraries (cgo
> -godefs), what particular "private" behavior am I depending on? Is it
> the ability to use `cgo -godefs` to figure out a Go equivalent for the
> C data structures, or is it counting on the
> ``uintptr(unsafe.Pointer(...))` to do the right thing, or something
> else?

It is "cgo -godefs". That is not supported for any use other than
generating the files used by the syscall and runtime packages. It may
work for you today but there is no guarantee that it will continue to
work.

Ian

George Hartzell

未读,
2019年8月30日 20:28:022019/8/30
收件人 hart...@alerce.com、Ian Lance Taylor、GoLang Nuts
George Hartzell writes:
> [...]
> ```
> i2c/i2c.go:15:9: cannot use &buf[0] (type *byte) as type *_Ctype_uchar
> in assignment
> ```
>
> I can't come up with any cast that makes the compiler happy. I'm also
> unsure whether that line runs afoul of this commandment: *Go code may
> not store a Go pointer in C memory* from https://golang.org/cmd/cgo/.
>

Hmmm. Well this works, and building/running with GODEBUG=cgocheck=2
doesn't complain, so perhaps it's ok?

```
[...]
buf := [2]C.__u8{}

im.addr = 0x18
im.flags = C.I2C_M_RD
im.len = C.__u16(len(buf))
im.buf = &buf[0]
[...]
```

g.

George Hartzell

未读,
2019年8月31日 13:04:552019/8/31
收件人 Ian Lance Taylor、hart...@alerce.com、GoLang Nuts
Ian Lance Taylor writes:
> [...]
> OK, that is true. Since your program will be using cgo, even though
> you aren't calling any C functions, it will be dynamically linked by
> default. You could try using "go build -ldflags=-extldflags=-static".
> I don't know whether it will work.
>

To close the loop on this, using

```
go build -ldflags=-extldflags=-static
```

does work, at least for the simple demo we've been discussing.

Onward!

Thanks again,

g.

George Hartzell

未读,
2019年8月31日 13:57:222019/8/31
收件人 hart...@alerce.com、GoLang Nuts
George Hartzell writes:
>
> Quick summary, I'm trying to understand the Go structures that cgo
> gives me, how they map back onto the C structures and why a Go struct
> that doesn't quite match the cgo output seems to work anyway.
> [...]

Ian gave me a lot of good things to chew on, but didn't answer the
main question about why the different versions of the `i2c_msg` struct
definition all seem to work.

For those of you who've been sitting on the edges of your seats
awaiting the answer...

It turns out that my assumption, that the `buf` pointer's alignment
requirements end up inserting padding automagically, seems to be true.

I convinced myself of this using the `structlayout` tools from
https://github.com/dominikh/go-tools.

Starting with this input file:

```go
package main

// #include <linux/i2c.h>
import "C"

// This is what `cgo -godefs` generates
type ViaGoDefs struct {
addr uint16
flags uint16
len uint16
buf *uint8
}

// This is what @tetsu-koba used
type FromTetsu struct {
addr uint16
flags uint16
len uint16
__padding uint16
buf uintptr
}

// This is the result of using cgo as Ian suggested
type ViaCGo C.struct_i2c_msg
```

you can see that all of the structs have the same shape:

```
pi@raspberrypi:~/temper/go/structures $ structlayout -json structures ViaGoDefs | structlayout-pretty
+--------+
0 | | <- ViaGoDefs.addr uint16 (size 2, align 2)
+--------+
2 | | <- ViaGoDefs.flags uint16 (size 2, align 2)
+--------+
4 | | <- ViaGoDefs.len uint16 (size 2, align 2)
+--------+
6 | | <- padding (size 2, align 0)
+--------+
8 | | <- ViaGoDefs.buf *uint8 (size 4, align 4)
+--------+
-........-
+--------+
11 | |
+--------+
pi@raspberrypi:~/temper/go/structures $ structlayout -json structures FromTetsu | structlayout-pretty
+--------+
0 | | <- FromTetsu.addr uint16 (size 2, align 2)
+--------+
2 | | <- FromTetsu.flags uint16 (size 2, align 2)
+--------+
4 | | <- FromTetsu.len uint16 (size 2, align 2)
+--------+
6 | | <- FromTetsu.__padding uint16 (size 2, align 2)
+--------+
8 | | <- FromTetsu.buf uintptr (size 4, align 4)
+--------+
-........-
+--------+
11 | |
+--------+
pi@raspberrypi:~/temper/go/structures $ structlayout -json structures ViaCGo | structlayout-pretty
+--------+
0 | | <- ViaCGo.addr structures._Ctype_ushort (size 2, align 2)
+--------+
2 | | <- ViaCGo.flags structures._Ctype_ushort (size 2, align 2)
+--------+
4 | | <- ViaCGo.len structures._Ctype_ushort (size 2, align 2)
+--------+
6 | | <- padding (size 2, align 0)
+--------+
8 | | <- ViaCGo.buf *structures._Ctype_uchar (size 4, align 4)
+--------+
-........-
+--------+
11 | |
+--------+
pi@raspberrypi:~/temper/go/structures $
```

Phew.

g.
回复全部
回复作者
转发
0 个新帖子