LoginSignup
4
3

More than 3 years have passed since last update.

Go の標準パッケージにないシステムコールを使う

Last updated at Posted at 2019-11-24

はじめに

Qiita 初投稿です。

Go の golang.org/x/sys には、OS のシステムコールの Wrapper 関数が定義されていますが、全てが網羅されているわけではありません。

例えば、windows package を見てみると、WSARecv() などはありますが、WSASocket() がない。

本項では、これを自前で定義する方法について述べます。

検証環境

  • Windows 10
  • go version go1.13.3 windows/amd64

Syscall の構造

まず、WSARecv() がどのように定義されているか見ていきます。
WSARecv() の定義は golang.org/x/sys/windows/zsyscall_windows.go にあります。

func WSARecv(s Handle, bufs *WSABuf, bufcnt uint32, recvd *uint32, flags *uint32, overlapped *Overlapped, croutine *byte) (err error) {
    r1, _, e1 := syscall.Syscall9(procWSARecv.Addr(), 7, uintptr(s), uintptr(unsafe.Pointer(bufs)), uintptr(bufcnt), uintptr(unsafe.Pointer(recvd)), uintptr(unsafe.Pointer(flags)), uintptr(unsafe.Pointer(overlapped)), uintptr(unsafe.Pointer(croutine)), 0, 0)
    if r1 == socket_error {
        if e1 != 0 {
            err = errnoErr(e1)
        } else {
            err = syscall.EINVAL
        }
    }
    return
}

はい、謎の関数 syscall.Syscall9() が現れました。
意味不明すぎて考えることをやめたくなりますが、恐れることはありません。ファイル先頭に次のようにある通り、これは自動生成コードです。

// Code generated by 'go generate'; DO NOT EDIT.

関数定義

同ディレクトリの syscall_windows.go に生成する関数の定義があります。

//sys   WSARecv(s Handle, bufs *WSABuf, bufcnt uint32, recvd *uint32, flags *uint32, overlapped *Overlapped, croutine *byte) (err error) [failretval==socket_error] = ws2_32.WSARecv

長いので細かく分けて見ていきます。

//sys

system call のコード生成であることを表す。このコメントを、後述の mkwinsyscall が拾う。

WSARecv(s Handle, bufs *WSABuf, bufcnt uint32, recvd *uint32, flags *uint32, overlapped *Overlapped, croutine *byte) (err error)

定義する関数シグネチャ。WSABuf とか Overlapped とかの型は、windows package で定義されている。

[failretval==socket_error]

system call が戻り値 socket_error を返したときにエラーとする。socket_error は windows package の package private な定数。

const socket_error = uintptr(^uint32(0))

MSDN より

If no error occurs and the receive operation has completed immediately, WSARecv returns zero. In this case, the completion routine will have already been scheduled to be called once the calling thread is in the alertable state. Otherwise, a value of SOCKET_ERROR is returned

= ws2_32.WSARecv

上述の関数を、ws2_32.dll 内の WSARecv 関数にマッピングする。

コード生成

生成コマンド定義は mksyscall.go にあります。

//go:generate go run golang.org/x/sys/windows/mkwinsyscall -output zsyscall_windows.go eventlog.go service.go syscall_windows.go security_windows.go

//go:generate go run までは、Go generate の書式。

golang.org/x/sys/windows/mkwinsyscallzsyscall_windows.go eventlog.go service.go syscall_windows.go security_windows.go から //sys の定義を拾い上げて、zsyscall_windows.go を生成しているようです。

自前で WSASocket() を定義してみる

仕組みはわかったので、自前で WSASocket() を定義してみます。

import "golang.org/x/sys/windows"

const (
    wsaprotocol_len    = 255
    max_protocol_chain = 7
    invalid_socket     = ^windows.Handle(0)
)

type GROUP uint32

type GUID struct {
    Data1 uint32
    Data2 uint16
    Data3 uint16
    Data4 [8]byte
}

type WSAPROTOCOLCHAIN struct {
    ChainLen     int32
    ChainEntries [max_protocol_chain]uint32
}

type WSAPROTOCOL_INFO struct {
    ServiceFlags1     uint32
    ServiceFlags2     uint32
    ServiceFlags3     uint32
    ServiceFlags4     uint32
    ProviderFlags     uint32
    ProviderID        GUID
    CatalogEntryID    uint32
    ProtocolChain     WSAPROTOCOLCHAIN
    Version           int32
    AddressFamily     int32
    MaxSockAddr       int32
    MinSockAddr       int32
    SocketType        int32
    Protocol          int32
    ProtocolMaxOffset int32
    NetworkByteOrder  int32
    SecurityScheme    int32
    MessageSize       uint32
    ProviderReserved  uint32
    Protocols         [wsaprotocol_len + 1]uint16
}

//go:generate go run golang.org/x/sys/windows/mkwinsyscall -output zwinsys.go winsys.go
//sys WSASocket(af int32, tp int32, protocol int32, protocolInfo *WSAPROTOCOL_INFO, g GROUP, flags uint32) (socket windows.Handle, err error) [failretval==invalid_socket] = ws2_32.WSASocketW

なにやら構造体やら色々でてきましたが、これらは C の構造体やマクロ定義を移植したものです。(この辺の情報を一々調べて定義するのが面倒。。。)

ここで出てきた WORD, DWORD, int の対応は次の通り。

C Go
WORD uint16
DWORD uint32
int int32

コード生成

//go:generate を記述した package のディレクトリで次のコマンドを実行すると、zmysys.go が生成されます。

$ go generate

実際に使ってみる

正常ケースとエラーケースを試してみます。
まずは正常ケース。

sock, err := WSASocket(windows.AF_INET, windows.SOCK_STREAM, windows.IPPROTO_TCP, nil, 0, 0)
fmt.Println(sock, err)
// => 344 <nil>

正しくSOCKETが生成され、エラーも発生していないようです。

次にエラーケース。

const badArg int32 = 9999
sock, err := WSASocket(badArg, badArg, badArg, nil, 0, 0)
fmt.Println(sock, err)
// => 18446744073709551615 The support for the specified socket type does not exist in this address family.

不正な値を与えたので、WSAESOCKTNOSUPPORT のエラーが返ってきました。
このメッセージは FormatMessageWで取得されたものです。

エラーコードは syscall.Syscall<N>() の3番目の戻り値として返ってきます。その実装は runtime のアセンブラで定義されていてコードを追いきれませんでしたが、GetLastError() の結果を返してきている模様。(たぶんこの辺)
本来、WSAXXX() のエラーコードは WSAGetLastError() で取得するものですが、WSAGetLastError()GetLastError() の Wrapper であるがため、問題なく動いているようです。

まとめ

標準package で定義されていない system call も、golang.org/x/sys/windows/mkwinsyscall を使えば、比較的簡単に呼び出し可能。

4
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
3