golang メモ

golang についてのメモ。適宜追加。

The Go Programming Language

目次

組み込み関数

ドキュメント

下記にまとまっている。

builtin - The Go Programming Language

string

アクセス方法ごとの返り値

...で展開した際はbyteが返るので、下記のようにしてbyte sliceにappendできる。

   a := []byte{}
    b := "hoge"
    a = append(a, b...)

stringは配列のようにアクセスするとbyteが返る。またstringのサイズをlenで求めた場合、byte数が返る。

一方で、for rangeで回すとruneが返る。

よって、下記のようにループの回し方でそれぞれ異なる値が返る。

package main

import "fmt"

func main() {
    test := "hoge"

    for i := 0; i < len(test); i++ {
        fmt.Printf("type=%T val=%x\n", test[i], test[i])
    }

    for _, v := range test {
        fmt.Printf("type=%T val=%c\n", v, v)
    }

}
type=uint8 val=68
type=uint8 val=6f
type=uint8 val=67
type=uint8 val=65
type=int32 val=h
type=int32 val=o
type=int32 val=g
type=int32 val=e

slice

lengthとcapacity

sliceはlengthとcapacity を持つ。それぞれlen() および cap() というビルトイン関数で取得できる。いずれも戻り値はint型。

makeする際にcapcityを省略すると、capacityの値はlengthと等しくなる。

   a := make([]int, 8)
    fmt.Printf("len=%d cap=%d\n", len(a), cap(a))
    // len=8 cap=8

sliceは参照型

参照型なので、代入してから変更すると、元のsliceも更新される。

func printSlicePointer() {
    a := []byte{0xaa}
    b := a

    fmt.Printf("a=%p b=%p\n a=%x\n b=%x\n", a, b, a, b)

    b[0] = 0x12

    fmt.Printf("a=%p b=%p\n a=%x\n b=%x\n", a, b, a, b)

}

これの結果は下記。アドレスならびにbへの修正がaに対しても行われたかのように見える。

a=0xc00001a110 b=0xc00001a110
 a=aa
 b=aa
a=0xc00001a110 b=0xc00001a110
 a=12
 b=12

これを避けるにはcopyを使用して、同一内容のsliceを別途作成する。

sliceのcopy

// copy元のslice が var src []byteとする
dst := make([]byte, len(src) )
copy(dst, src)

dstに十分なlengthがない場合、copyでpanicする。

sliceの一部を参照する

dst := src[start:end]

これによって、src[start] から src[end-1]の要素を参照する slice dst が生成される。len(dst) は end - startとなる。

参照なので、dstを修正した場合は、src側も修正される。

startとendは省略も可能。startを省略した場合は先頭から、endを省略した場合は最後尾まで、という意味になる。

array

宣言

下記のように書くことで、要素数コンパイラが決めてくれる。

   a := [...]int{0, 1, 2, 3}

arrayを参照するsliceの生成

slice := array[:]

下記のようなコードを実行すると、sliceとarrayで同じアドレスを指しており、sliceはarrayの参照であることがわかる。

   a := [4]int{0, 1, 2, 3}
    b := a[:]
    fmt.Printf("a=%p b=%p\n a=%x\n b=%x\n", &a, b, a, b)

この結果は下記。

a=0xc000014460 b=0xc000014460
 a=[0 1 2 3]
 b=[0 1 2 3]

interface

cast

vInt, ok := i.(int)

switch

   switch v := i.(type) {
    case int:
        fmt.Printf("int\n")
    case string:
        fmt.Printf("string\n")
    default:
        fmt.Printf("unknown\n")
    }

構造体のTag

encoding/json でよく見る。`key:"val"`

   type sample struct {
        i int     `json:"int"`
        f float32 `sample:"float"`
    }

バッククォート内ではスペースをつけて複数のタグを付与することもできる。

   type sample struct {
        i int     `json:"int" sample:"aaa"`

The Go Programming Language Specification - The Go Programming Language

タグの取得

reflectパッケージを使用する。reflect.StructFieldを取得し、そのメンバのTagを参照する。

  1. TypeOfでTypeを取得
  2. Kindで構造体か判別し、NumFieldで構造体個数を確認する。
  3. Fieldで構造体メンバを取得し、TagメンバのStructTagに対して、Get/Lookupする。
func readStructTag() {
    type S struct {
        V int    `sample:"V"`
        W string `sample:"W"`
        I uint
        B float32 `sample:""`
    }
    s := S{}
    t := reflect.TypeOf(s)
    for i := 0; i < t.NumField(); i++ {
        f := t.Field(i)
        v, ok := f.Tag.Lookup("sample")
        if ok {
            fmt.Printf("%d:value=%s\n", i, v)
        }
    }
}
0:value=V
1:value=W
3:value=

reflect - The Go Programming Language

cgo

公式Wiki

cgo · golang/go Wiki · GitHub

テスト

$ go test

サブディレクトリ以下もテストをする場合は、./...を付与する。

$ go test ./...

コメントによる標準出力のテスト

// Output: というコメントを付与し、改行後想定される出力をコメントで記述する。 // の後にはスペースを挟む。

// Output:
// 0x0f

出力内容が順不同の場合は、// Unordered output: とする。

testing - The Go Programming Language

ベンチマーク

BenchmarkXxx(*testing.B) という関数を定義する。 Xxxとあるように、大文字である必要がある。

testing - The Go Programming Language

go test にオプションを付与することでベンチマークが測定される。

go - The Go Programming Language

メモリ使用量やアロケート回数を調べたい場合は下記。

go test -bench . --benchmem

プロファイルを取得する

[Go] pprofでのプロファイル(計測)のやり方を改めて整理した - Qiita

go test時に下記オプションを付与することで、go tool pprof 用のファイルを生成できる。

go test -cpuprofile cpu.prof -memprofile mem.prof -bench .

ビルド

Cから使える共有ライブラリをビルドする

-buildmode=c-shared を付与する。

go - The Go Programming Language

最適化状況を確認する

-gcflags "-m" をつける。

Goのコンパイル時の最適化結果を確認する(インライン化の条件についても記載) - Qiita

最適化されるための条件。

CompilerOptimizations · golang/go Wiki · GitHub

コンパイルオプション一覧

compile - The Go Programming Language

golang 自体のビルド

リポジトリをチェックアウトして、バージョンbranchにスイッチ。

$ git clone https://go.googlesource.com/go goroot
$ cd goroot
$ git checkout go1.XX.X
$ cd src
$ ./all.bash

参考資料

Golangでバイナリ等のbit操作を行うためのライブラリ go-bit

加筆修正したものをこちらに書きました。v2.2.0からbinary.Write相当のAPI、bit.Writeをサポートしました。

Golangでバイナリ等のbit操作を行うためのライブラリ go-bit | Zenn

概要

go で 主にバイナリファイルの読み出し等に使えるbit操作ライブラリを作りました。*1

binary.Readのように、構造体にbit配列を定義して読みだすことができます。

github.com

目次

特徴

下記のようにbitを定義でき、bit.Readを使用することでいい感じに埋めて返してくれます。

package main

import (
    "bytes"
    "encoding/binary"
    "fmt"
    "github.com/nokute78/go-bit/pkg/bit/v2"
    "io"
)

type BitMap struct {
    Bit0     bit.Bit
    Bit1     bit.Bit
    Bit2     bit.Bit
    Bit3     bit.Bit
    Reserved [4]bit.Bit `bit:"skip"`
}

func ReadBitMap(r io.Reader, b binary.ByteOrder) (*BitMap, error) {
    ret := &BitMap{}
    if err := bit.Read(r, b, ret); err != nil {
        return nil, err
    }
    return ret, nil
}

func main() {
    buf := bytes.NewBuffer([]byte{0xf5})
    bm, err := ReadBitMap(buf, binary.LittleEndian)
    fmt.Printf("bm=%+v err=%s\n", bm, err)

}

作った背景

レジスタやバイナリファイルの仕様を見ていると、bitごとに意味が割り当てられていて、これらの意味を解釈するためには、byte配列等で読みだした上で&や|の演算子を駆使し、bit演算をする必要があります。

また、golangにはmath/bits というbit操作用のパッケージがあるのですが、これは主に演算用のもので、上記のようなbitの取り出しには不向きなようでした。(四則演算やその際にキャリーするとか、ビットの並べ替えをするとか、そういう用途のようです。)

また、goにはencoding/binaryという便利なパッケージがあり、定義済の構造体をbinary.Readに投げれば、バイナリファイルをいい感じに解釈してその構造体に埋めて返してくれる機能があります。

まあ、binary.Readのbit版が欲しかったのです。構造体にbitの定義をして、投げればいい感じに埋めて返してくれるAPIが。

インストール

下記のようにv2をgo get してください。(トップディレクトリのものは古いです)

go get github.com/nokute78/go-bit/pkg/bit/v2

StuctTag

いくつかのStruct Tagをサポートしています。

タグ 内容
`bit:"skip"` このフィールドは無視します。オフセットはフィールドサイズ分だけ移動します。Reservedな値に対して使うと良いです。
`bit:"-"` このフィールドは無視します。オフセットは移動しません。
`bit:"BE"` このフィールドはBigEndianとして扱う。Mixed Endianなデータの解析に便利。(ただしbit向きでなく、後述のbyte array用)
`bit:"LE"` このフィールドはLittleEndianとして扱う。Mixed Endianなデータの解析に便利。(ただしbit向きでなく、後述のbyte array用)

サンプルコード

zipファイルにはヘッダにgeneral purpose bit flagという16bitのデータ構造を持っているので、それを読んでみましょう。 *2

ファイルヘッダについては4.3.7 、general purpose bit flagについては、下記の4.4.4を参照しました。 https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT

下記はサンプルコードです。

zip file header を構造体として定義し、それをbit.Readに渡しているのがポイントです。

package main

import (
    "encoding/binary"
    "flag"
    "fmt"
    "github.com/nokute78/go-bit/pkg/bit/v2"
    "os"
)

type ZipHeader struct {
    Signature  uint32
    MinVersoin uint16
    // general purpose bit flag: 2bytes
    Encrypted           bit.Bit
    CompMode            [2]bit.Bit
    Crc32CompUnCompUsed bit.Bit
    ReservedForMethod8  bit.Bit `bit:"skip"`
    CompPatchedData     bit.Bit
    StrongEncryption    bit.Bit
    Unused              [4]bit.Bit `bit:"skip"`
    LanguageEncoding    bit.Bit
    Reserved            bit.Bit
    HideLocalHeader     bit.Bit
    Reserved2           [2]bit.Bit `bit:"skip"`
    CompMethod          uint16
    LastModTime         uint16
    LastModData         uint16
    Crc32               uint32
    CompSize            uint32
    UnCompSize          uint32
    FileNameLen         uint16
    ExtraFieldLen       uint16
}

func main() {
    flag.Parse()

    var zh ZipHeader
    for _, v := range flag.Args() {
        f, err := os.Open(v)
        if err != nil {
            fmt.Fprintf(os.Stderr, "Open(%s): err=%s\n", v, err)
            continue
        }

        err = bit.Read(f, binary.LittleEndian, &zh)
        if err != nil {
            fmt.Fprintf(os.Stderr, "Read(%s): err=%s\n", v, err)
            f.Close()
            continue
        }
        fmt.Printf("header(%s):%+v\n", v, zh)
        f.Close()
    }

}

go のインストールディレクトリにはテスト用のzipファイルがいくつか有ったので、試しに読んでみました。

$ go run zipexample.go /usr/local/go/src/archive/zip/testdata/dd.zip 
header(/usr/local/go/src/archive/zip/testdata/dd.zip):{Signature:67324752 MinVersoin:20 Encrypted:0 CompMode:[0 0] Crc32CompUnCompUsed:1 ReservedForMethod8:0 CompPatchedData:0 StrongEncryption:0 Unused:[0 0 0 0] LanguageEncoding:0 Reserved:0 HideLocalHeader:0 Reserved2:[0 0] CompMethod:8 LastModTime:26826 LastModData:15938 Crc32:0 CompSize:0 UnCompSize:0 FileNameLen:8 ExtraFieldLen:0}

Bit3(Crc32CompUnCompUsed)が立っていますね。これが立っているとcrc-32/compressed size/uncompressed size が0になるそうです。それらがキチンと0になっている様子が見られます。

下記のようにBit3が落ちている場合の例も貼っておきます。これらはcrc-32や各サイズが非0ですね。

$ go run zipexample.go /usr/local/go/src/archive/zip/testdata/unix.zip 
header(/usr/local/go/src/archive/zip/testdata/unix.zip):{Signature:67324752 MinVersoin:10 Encrypted:0 CompMode:[0 0] Crc32CompUnCompUsed:0 ReservedForMethod8:0 CompPatchedData:0 StrongEncryption:0 Unused:[0 0 0 0] LanguageEncoding:0 Reserved:0 HideLocalHeader:0 Reserved2:[0 0] CompMethod:0 LastModTime:20620 LastModData:16264 Crc32:2098461837 CompSize:8 UnCompSize:8 FileNameLen:5 ExtraFieldLen:28}

なお、README.mdではBig Endianでも動作するか確認するため、一例としてTCPHeaderのパースを行っています。

そのほか

golangのbinary.Readでは、例えば6byteをBig Endianで読むということができないようです。

https://play.golang.org/p/RUvRdkc0a0W

https://github.com/golang/go/issues/40891

数値型のbyteサイズでエンディアンが判定される様子。

bit.Readではbyte arrayを渡せばエンディアンを反映して埋めてくれるようにしてあり、そこはbinary.Readと非互換です。

*1:エンディアンの理解が怪しかったので結構苦労しました。。

*2:あくまで参考例として。golangには "archive/zip" にZipのFileHeaderが定義されているので、通常はこういう定義はしないでしょうけれど。

Go1.15 からのreflectパッケージの挙動の違いについて

追記

Go1.15 からのreflectパッケージの挙動の違いについて | Zenn にも掲載してみました。

概要

Go1.15からreflectパッケージの一部挙動が変わった。これにより、Go1.14以前で動作していたコードがPanicしうる。

非公開構造体を埋め込み、その構造体のメソッドをCallした場合にPanicする。

目次

リリースノートの記述

golang.org

Go 1.15のリリースノートによると、reflectパッケージについて下記記述がありました。

Package reflect now disallows accessing methods of all non-exported fields, whereas previously it allowed accessing those of non-exported, embedded fields. Code that relies on the previous behavior should be updated to instead access the corresponding promoted method of the enclosing variable.

サンプルコード

サンプルコードは下記です。二つの構造体を定義しています。

  • 非公開のunexportedStruct Printするだけのメソッドを持つ。
  • 公開のR 上記unexportedStructが埋め込まれている。

Rを用いて、unexportedStruct内のメソッドにアクセスしようとするコードです。エラー処理は省いています。

package main

import (
    "fmt"
    "reflect"
)

type unexportedStruct struct{}

func (s unexportedStruct) Print() {
    fmt.Println("Print!")
}

type R struct {
    unexportedStruct
}

func main() {
    v := reflect.ValueOf(R{})
    fmt.Printf("NumMethod=%d\n", v.Field(0).NumMethod())

    method := v.Field(0).Method(0)
    fmt.Printf("Method=%v\n", method)
    method.Call(nil)
}

Go1.14とGo1.15の違い

下記が挙動の違いです。Go 1.14.7 ではPrint関数が実行されるのに対し、Go1.15ではPanicして落ちます。

Go 1.15

$ go version
go version go1.15 linux/amd64
$ go run unexported.go 
NumMethod=1
Method=0x48f340
panic: reflect: reflect.Value.Call using value obtained using unexported field

goroutine 1 [running]:
reflect.flag.mustBeExportedSlow(0x2b3)
    /usr/local/go/src/reflect/value.go:237 +0x131
reflect.flag.mustBeExported(...)
    /usr/local/go/src/reflect/value.go:228
reflect.Value.Call(0x4dd980, 0x5daf48, 0x2b3, 0x0, 0x0, 0x0, 0x1, 0x10, 0x0)
    /usr/local/go/src/reflect/value.go:335 +0x52
main.main()
    /home/taka/tmp/unexported.go:22 +0x1ef
exit status 2

Go 1.14

$ go version
go version go1.14.7 linux/amd64
$ go run unexported.go 
NumMethod=1
Method=0x488b20
Print!

修正コミット

この辺ですかね。

github.com

所感

Go1.14までの挙動がバグっぽいので適切に修正されたのかなと。

けれどもユーザーとしてはPanicするか否かをどう判別するのがいいんでしょうかね。いずれもNumMethod()の返り値は同じですし、CanCallのようなチェック関数もないですし。

fluent-bit の lua filter plugin 使用例

概要

fluentbit.io

fluent-bit には Lua スクリプトでfilterできるプラグインがあります。 これを使うと、下記のようなことができます。

  • recordに情報を追加する
  • 特定の条件にマッチしたrecordをフィルタする
  • record内の特定の情報を削除する

目次

使用方法

fluent-bitにはLuaスクリプトのパスと、スクリプトに記載されている関数名を伝える必要があります。 たとえば、下記の例は、test.luaスクリプトをロードし、そこに含まれるtest_func関数を実行するという意味になります。

[INPUT]
    Name dummy
    Tag  test
    Dummy  {"foo":"bar"}

[FILTER]
    Name   lua
    Match  *
    script ./test.lua
    call   test_func

[OUTPUT]
    Name  stdout
    Match *

制約

Fluent-bitのLuaは数値型としてdoubleを使用しています。 そのため、下記のような問題が発生する可能性があります。

前者については、Type_int_key というプロパティを指定することで、フィルタ後の値を再度整数型に変更してくれるようになります。

Lua - Fluent Bit: Official Manual

後者については、良い解が思いついていません。パッチをお待ちしております。

Lua filters can mangle timestamps · Issue #2015 · fluent/fluent-bit · GitHub

luaスクリプト側の仕様

下記のように引数と戻り値がそれぞれ3つずつ必要です。

function test_func(tag, timestamp, record)
     -- something to do
    return ret, timestamp, new_record
end

fluentdのeventを構成するtag, timestampとLua連想配列形式のrecordが与えられ、戻り値としてfilter結果を示すretとtimestamp、そしてrecordを返却します。 ここで、retはfilter結果に応じて、下記のいずれかを返す必要があります。

filter結果 retの値 備考
削除した -1 不要なeventをフィルタリングする用途
何も変更しなかった 0
recordとtimestampを変更した 1
recordのみ変更した 2 v1.4.3より対応。timestampの丸め誤差を防止するための機能。

使用例

それでは使用例を紹介していきます。

時刻情報をrecordに加える

フィルタした時刻を保持したい場合には下記のように行います。

configuration

test.lua:

function test_func(tag, timestamp, record)
    new_record = record
    new_record["filterd_time"] = os.time()
    new_record["filterd_date"] = os.date()
    return 2, timestamp, new_record
end

input

{"foo"=>"bar"}

output

{"filterd_date"=>"Sun May 31 09:10:59 2020", "foo"=>"bar", "filterd_time"=>1590883859.000000}

タグをrecordに加える

tagというkey名でtagの文字列を追加するスクリプトです。

configuration

test.lua:

function test_func(tag, timestamp, record)
    new_record = record
    new_record["tag"] = tag
    return 2, timestamp, new_record
end

test.conf:

[INPUT]
    Name dummy
    Tag  test
    Dummy  {"foo":"bar"}

[FILTER]
    Name   lua
    Match  *
    script ./test.lua
    call   test_func

[OUTPUT]
    Name  stdout
    Match *

input

{"foo"=>"bar"}

output

{"foo"=>"bar", "tag"=>"test"}

"tag"=>"test" が付与されています。

特定のkey/value ペアを削除する

record内の一部ペアを消去する例です。

configuration

test.conf:

[INPUT]
    Name dummy
    Tag  test
    Dummy  {"important":"value", "drop":"value"}

[FILTER]
    Name   lua
    Match  *
    script ./test.lua
    call   test_func

[OUTPUT]
    Name  stdout
    Match *

test.lua:

function test_func(tag, timestamp, record)
     if record["drop"] == nil then
        return 0, timestamp, record
     end
     new_record = record
     new_record["drop"] = nil
     return 2, timestamp, new_record
end

input

{"important"=>"value", "drop"=>"value"}

output

{"important"=>"value"}

"drop":"value" というペアが消えています。

特定のkeyを含むrecordを捨ててフィルタする

例えば不要なデータ通信を行わないように、record内に特定のkeyが含まれていた場合にそのrecord全体を捨てるユースケースがあるかと思います。

下記は、"drop" というkeyが含まれている場合にrecord全体を捨てる例になります。

configuration

[INPUT]
    Name dummy
    Tag  test
    Dummy  {"important":"value"}

[INPUT]
    Name dummy
    Tag  test
    Dummy  {"drop":true}

[FILTER]
    Name   lua
    Match  *
    script ./test.lua
    call   test_func

[OUTPUT]
    Name  stdout
    Match *

test.lua:

function test_func(tag, timestamp, record)
     key = "drop"
     if record[key] ~= nil then
        return -1, timestamp, record
     end
     return 0, timestamp, record
end

input

{"important"=>"value"}
{"drop"=>true}

output

{"important"=>"value"}

{"drop":true} というrecordが出力されなくなるかと思います。

特定のkey名を変更する

configuration

test.conf

[INPUT]
    Name dummy
    Tag  test
    Dummy  {"foo":"bar"}

[FILTER]
    Name   lua
    Match  *
    script ./test.lua
    call   test_func

[OUTPUT]
    Name  stdout
    Match *

test.lua:

function test_func(tag, timestamp, record)
     new_record = record
     if record["foo"] == nil then
        return 0, timestamp, new_record
     end
     new_record["hoge"] = record["foo"]
     new_record["foo"] = nil
     return 2, timestamp, new_record
end

input

{"foo"=>"bar"}

output

{"hoge"=>"bar"}

"foo"というkeyが"hoge"に変わっています。

UEFI Shell の コマンド

概要

UEFI Shellについてのメモ。

目次

UEFI Shellとは

UEFI Firmware(BIOSに相当)上で動作するShellのこと。 簡単なファイル操作をはじめ、NW通信なども行うことができる。

UEFI Shell の動作環境構築

Ubuntu 20.04上のQEMU上でShellを試す

インストール方法

sudo apt install qemu-kvm

起動方法

qemu-system-x86_64 -bios OVMF.fd

Shell上からホスト上のディレクトリにアクセスできるようにする

-driveオプションで指定する。

qemu起動後、ホストからディレクトリの内容を書き換えても、UEFI Shell上からは認識できないので注意すること。

qemu-system-x86_64 -bios OVMF.fd -drive file=fat:rw:path/to/dir

リンク

ソース

この辺。 github.com

ユースケース

help

コマンドの一覧 や 各コマンドの内容について調べる

helpコマンドを使う。

引数無しの場合はサポートしているコマンド一覧を確認できる。

help

コマンド名を引数に与えることでそのコマンドのhelpを参照できる。

help ls

ファイル/ディレクト

ファイルパスについて

Windowsライク。 パスの区切りはバックスラッシュ'\'。 また、先頭にボリュームラベル名を付与することで、そのマップ以下のディレクトリやファイルを指定できる。(Windowsのドライブレターのよう)

下記はボリュームラベルfs0以下にマップされているhogeディレクトリを指定する絶対パス

fs0:\hoge

ワイルドカードもサポートされている。

マウント可能な一覧を確認する(map)

> map
Mapping table
      FS0: Alias(s):HD0a1:;BLK1:
          PciRoot(0x0)/Pci(0x1,0x1)/Ata(0x0)/HD(1,MBR,0xBE1AFDFA,0x3F,0xFBFC1)
     BLK0: Alias(s):
          PciRoot(0x0)/Pci(0x1,0x1)/Ata(0x0)
     BLK2: Alias(s):
          PciRoot(0x0)/Pci(0x1,0x1)/Ata(0x0)

ファイルシステムをマウントする

下記のようにShell>という表示の場合はファイルシステムがマウントされていない。

Shell>

マウントしたい対象にコロンを加えた文字列を入力することでマウントできる。

下記はfs0をマウントする場合の例。

fs0:

マウントされた場合は、Shell>という表記が変わり、ボリュームラベル名:ディレクトリパスという表記になる。

fs0:\>

ディレクトリの一覧を表示する(ls/dir)

>ls
Directory of: FS0:\
05/29/2020  22:48              11,077  NvVars
05/29/2020  22:51                   2  ls.txt
          2 File(s)      11,079 bytes
          0 Dir(s)

dirコマンドもあるが、lsへのエイリアスとなっている。

ファイルのコピー(cp/copy)/消去(rm/del)/移動・リネーム(mv/move)

UNIXコマンドのように使用可能。

コピー操作。

cp src dst

リネーム操作。

mv aaa bbb

消去操作。

また、rmエイリアスとして、delが設定されている。

rm src

ディレクトリの作成(mkdir/md)/消去(rm)

hogeディレクトリの作成例は下記。

mkdir hoge

mkdirエイリアスとしてmdが用意されている。

ディレクトリの消去はrmdirでなく、rmコマンドを使用する。

rmdir hoge

ファイル内容を出力する(type/cat)

ファイル内容を標準出力に表示することができる。

type filepath

typeエイリアスとしてcatが設定されている。

タイムスタンプの更新(touch)

パス先のファイルのタイムスタンプを更新する。

なお、このコマンドで空ファイルの作成は行えないようだ。

touch filepath

ネットワーク

ネットワーク設定(ifconfig)

ifconfig コマンドを使う。 -lオプションでinterface名を取得して、-sコマンドと組み合わせてIPを割り当てる。

下記はeth0について、DHCPで割り当てる例。

ifconfig -s eth0 dhcp

設定の確認は-lオプション。

ifconfig -l

疎通確認(ping)

ping

ユーティリティ

テキストエディタ(edit)

フルスクリーンのエディタが立ち上がる。 ショートカットは下記の様子。

Ctrl+Tを押下することで、ASCIIかUNICODEかを切り替えることができる。

機能 ショートカット
終了 Ctrl+Q
ファイル保存 Ctrl+S
ファイルオープン Ctrl+O
検索 Ctrl+F
置換 Ctrl+R
カット Ctrl+K
ペースト Ctrl+U
行移動 Ctrl+G
文字コード切り替え Ctrl+T
ヘルプ Ctrl+E

バイナリエディタ(hexedit)

カット/ペーストがテキストエディタと異なるので注意。

ファイルのほか、-d でディスクを、-mでメモリもオープンできるらしい。

機能 ショートカット
終了 Ctrl+Q
バッファ保存 Ctrl+S
カット Ctrl+X
ペースト Ctrl+V
選択開始 Ctrl+T
選択終了 Ctrl+D
ファイルオープン Ctrl+O
ディスクオープン Ctrl+I
メモリオープン Ctrl+M
オフセット移動 Ctrl+G
ヘルプ Ctrl+E

システム情報

エイリアス設定(alias)

オプション無しで、現在のエイリアスを確認できる。

>alias
         cat:type
        cd..:cd ..
         cd\:cd \
        copy:cp
         del:rm
         dir:ls
          md:mkdir
         mem:dmem
       mount:map
        move:mv
         ren:mv

UEFI Shellや UEFIのバージョンを確認する(ver)

UEFI Shellのバージョンと、UEFI ShellがアクセスするUEFI 自体のバージョンを確認できる。

> ver
UEFI Interactive Shell v2.2
EDK II
UEFI v2.70 (EDK II, 0x00010000)

UEFI変数を操作する(dmpstore)

引数無しの場合、すべてのUEFI変数とその属性ならびに内容がダンプされる。

dmpstore

消去を行う場合は、-dオプション。

タイムゾーンの表示/変更(timezone)

オプションが無い場合、現在のタイムゾーンが表示される。

>timezone
LOCAL

タイムゾーンの一覧は-lオプションで確認できる。

timezone -l

タイムゾーンの設定は、-sオプションとUTCからのオフセットで設定できる。

timezone -s -9:00

時刻の確認/変更(time)

オプションが無い場合、現在時刻とUTCのオフセットが表示される。

>time
00:34:52 (UTC-09:00)

時刻設定は引数にhh:mm:ssのフォーマットで指定できる。

time 12:00:59

日付の確認/変更(date)

オプションが無い場合、現在の日付が表示される。フォーマットは、MM/DD/YYYY

> date
05/29/2020

引数で日付情報を与えることで変更できる。

date 06/30/2020

文字列や環境変数の表示(echo)

UNIXのように、文字列や環境変数の表示を行える。

また、スクリプトファイル実行時の出力制御の設定変更を行うことができる。

> echo hoge
hoge

環境変数表示の例は下記。変数名を%でくくる。

> echo %lasterror%
0xE

引数無しの場合は、スクリプト実行時の出力設定を確認できる。 この設定は、-on もしくは -offオプションで変更できる。

> echo
Echo is on.

環境変数の確認/設定(set)

オプション無しの場合、現在の環境変数一覧を確認できる。

> set

変数名と内容を下記のように指定できる。

set key val

ディスク容量を確認する(vol)

引数無しで、現在のボリュームラベル上の使用量や空き容量を確認できる。

> vol

ボリュームラベル名を付与することで、そのボリューム以下の情報を確認できる。

> vol fs0
Volume QEMU VVFAT (rw)
528171008 bytes total disk space
528056320 bytes available on disk
8192 bytes in each allocation unit

ハードウェア

PCIバイスの一覧表示(pci)

何もオプションがない場合、簡素な一覧が表示される。

> pci
   Seg  Bus  Dev  Func
   ---  ---  ---  ----
    00   00   00    00 ==> Bridge Device - Host/PCI bridge
             Vendor 8086 Device 1237 Prog Interface 0
    00   00   01    00 ==> Bridge Device - PCI/ISA bridge
             Vendor 8086 Device 7000 Prog Interface 0
    00   00   01    01 ==> Mass Storage Controller - IDE controller
             Vendor 8086 Device 7010 Prog Interface 80
    00   00   01    03 ==> Bridge Device - Other bridge type
             Vendor 8086 Device 7113 Prog Interface 0
    00   00   02    00 ==> Display Controller - VGA/8514 controller
             Vendor 1234 Device 1111 Prog Interface 0
    00   00   03    00 ==> Network Controller - Ethernet controller
             Vendor 8086 Device 100E Prog Interface 0

バス番号、デバイス番号、ファンクション番号を付与することで、対象のコンフィグ空間の情報を確認できる。 さらに-iオプションを付与することで人が読みやすい情報を付与して表示してくれる。

> pci 00 00 00 -i
  PCI Segment 00 Bus 00 Device 00 Func 00 [EFI 0000000000]
  00000000: 86 80 37 12 07 00 00 00-02 00 00 06 00 00 00 00  *..7.............*
  00000010: 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  *................*
  00000020: 00 00 00 00 00 00 00 00-00 00 00 00 F4 1A 00 11  *................*
  00000030: 00 00 00 00 00 00 00 00-00 00 00 00 FF 00 00 00  *................*

  00000040: 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  *................*
  00000050: 00 00 00 00 00 00 00 10-00 00 01 00 00 00 00 00  *................*
  00000060: 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  *................*
  00000070: 00 00 02 00 00 00 00 00-00 00 00 00 00 00 00 00  *................*
  00000080: 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  *................*
  00000090: 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  *................*
  000000A0: 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  *................*
  000000B0: 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  *................*
  000000C0: 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  *................*
  000000D0: 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  *................*
  000000E0: 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  *................*
  000000F0: 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  *................*

Vendor ID(0): 8086     Device ID(2): 1237
Command(4): 0007
  (00)I/O space access enabled:       1  (01)Memory space access enabled:    1
  (02)Behave as bus master:           1  (03)Monitor special cycle enabled:  0
  (04)Mem Write & Invalidate enabled: 0  (05)Palette snooping is enabled:    0
  (06)Assert PERR# when parity error: 0  (07)Do address/data stepping:       0
  (08)SERR# driver enabled:           0  (09)Fast back-to-back transact...:  0

Status(6): 0000
  (04)New Capabilities linked list:   0  (05)66MHz Capable:                  0
  (07)Fast Back-to-Back Capable:      0  (08)Master Data Parity Error:       0
  (09)DEVSEL timing:               Fast  (11)Signaled Target Abort:          0
  (12)Received Target Abort:          0  (13)Received Master Abort:          0
  (14)Signaled System Error:          0  (15)Detected Parity Error:          0

Revision ID(8): 02 BIST(0F):  Incapable
Cache Line Size(C): 00                 Latency Timer(D): 00
Header Type(0E):    00, Single function, PCI device
Class: Bridge Device - Host/PCI bridge - 

終了/シャットダウン/再起動

UEFI Shell を終了する(exit)

exit

シャットダウン/再起動する(reset)

引数無しの場合、再起動処理が実行される。

reset

シャットダウンを行う場合は、-sオプションを付与する。

reset -s

汎用的な情報

コマンドの出力をページ毎にいったん止める

おおよそのコマンドは -b オプションを付与することで、 一ページ単位で表示を止めてくれるようになる。 表示が止まったのち、Enterで改ページ、qでキャンセルを行うことができる。

エスケープシーケンス

^特殊文字エスケープできる。

下記はスペースを含むディレクトリ生成の例。

mkdir aaa^ bbb

ファイルへのリダイレクト

> によってファイルにリダイレクトできる。

dmpstore > dmpstore.txt

シェルスクリプト

シェルスクリプトの拡張子は.nshである。 UEFI Shell起動後、一定時間が経過した際に、startup.nsh というファイルが存在する場合は、そのファイルが自動実行される。

MessagePackを解析するためのライブラリ/ツール msgpack-microscope

概要

Golangで、MessagePackの解析用ライブラリとそれを使用したサンプルツールを作成しました。

github.com

ビルド済みバイナリはReleases から取得できます。

MessagePackとは

JSONのようなデータ構造を、バイナリ形式で表現したデータ形式です。 大抵の場合、JSONよりもデータサイズを減らすことができるようですが、バイナリ列のために可読性が下がっています。 公式サイトの例ですと、

{"compact":true,"schema":0}

という27byteのJSONを、

82 a7 63 6f 6d 70 61 63 74 c3 a6 73 63 68 65 6d 61 00

という18byteのバイナリに変換する例が示されています。

特徴

基本的には、インスパイア元のmsgpack-inspectとほぼ同じですが、下記が異なっています。 *1

サンプルツール msgpack2json

github.com

その名のとおり、MessagePackをJSONに変換するためのサンプルツール。 出力フォーマットは、同じ機能のツールであるmsgpack-inspectの形式をほぼ流用しています。

下記のいずれかからMessagePackのバイト列を受け取り、標準出力に解析結果を出力します。

  • 標準入力(他コマンドからパイプで繋ぐケース)
  • ファイル
  • HTTP POST

それぞれの動作サンプルについて以下に書きます。なお、いずれのケースについても下記JSON相当のMessagePackを解析した結果になります。

{"compact":true,"schema":0}

標準入力から読む

パイプでリダイレクトするケースを想定した使い方です。

$ printf "\x82\xa7compact\xc3\xa6schema\x00" | ./msgpack2json

出力結果は下記。

{"format":"fixmap", "header":"0x82", "length":2, "raw":"0x82a7636f6d70616374c3a6736368656d6100", "value":
    [
        {"key":
            {"format":"fixstr", "header":"0xa7", "raw":"0xa7636f6d70616374", "value":"compact"},
         "value":
            {"format":"true", "header":"0xc3", "raw":"0xc3", "value":true}
        },
        {"key":
            {"format":"fixstr", "header":"0xa6", "raw":"0xa6736368656d61", "value":"schema"},
         "value":
            {"format":"positive fixint", "header":"0x00", "raw":"0x00", "value":0}
        }
    ]
}

ファイルを読む

バイナリファイルを読むケースです。

$ printf "\x82\xa7compact\xc3\xa6schema\x00" > b.msgp
$ ./msgpack2json b.msgp 

出力結果は標準入力と同様なので割愛。

HTTP POST

簡単なhttp server機能も備えています。-sオプションでserver動作をし、デフォルトで8080ポートへのPOSTを待ちます。 なお、ポート番号については、-p オプションで変更することができます。

$ printf "\x82\xa7compact\xc3\xa6schema\x00" > b.msgp
$ ./msgpack2json -s &

たとえば、curlで下記のように投げることで出力結果を得ることができます。

$ curl -sS localhost:8080 -X POST --data-binary "@b.msgp"

Fluent-bitのout_httpプラグインなど、HTTP POST形式でMessagePackを投げるソフトウェアのデバッグなどにも使うことができます。

その他オプションについて

  • -e : Fluentd の EventTime Ext Format を解釈可能とする。

Fluentdでは、Ext Typeを使用して、独自のタイムスタンプ形式を定義しています。このオプションを使うことで、そのタイムスタンプ形式を解釈できるようになります。

$ printf "\xd7\x00\x5c\xda\x05\x00\x00\x00\x00\x00"| ./msgpack2json -e
{"format":"event time", "header":"0xd7", "type":0, "raw":"0xd7005cda050000000000", "value":"2019-05-14 09:00:00 +0900 JST"}
  • -r: プレーンなJSON形式で出力する

解析用でない、シンプルな形式で出力します。

$ printf "\x82\xa7compact\xc3\xa6schema\x00"|./msgpack2json -r
{"compact":true,"schema":0}

ライブラリ

基本的には、Decode関数にMessagePackの[]byteを与えることで、MPObjectに変換し、そのMPObjectの中身をゴニョゴニョして解析する形になります。

type MPObject struct {
    FirstByte byte        /* どのフォーマットかを示すbyte */
    FormatName  string  /* フォーマット名称 */
    ExtType   int8       /* Ext フォーマットの場合、どのTypeかを示す。それ以外のフォーマットの場合は0。*/
    Length    uint32  /* length を備えるフォーマットの場合のみ。*/
    DataStr   string  /* 各フォーマットのデータを文字列にしたもの */
    Raw       []byte /* FirstByteからデータ本体までのbyteスライス。ArrayやMapの場合は、その要素も含む。 */
    Child     []*MPObject /* ArrayやMap フォーマットの場合、その各要素が保存される。*/
}

import

import "github.com/nokute78/msgpack-microscope/pkg/msgpack"

サンプルコード

簡単なサンプルコードは下記です。

package main

import (
    "bytes"
    "fmt"
    "github.com/nokute78/msgpack-microscope/pkg/msgpack"
    "os"
)

func showMsgPack(obj *msgpack.MPObject) {
    switch {
    case msgpack.IsMap(obj.FirstByte) || msgpack.IsArray(obj.FirstByte):
        for _, v := range obj.Child {
            showMsgPack(v)
        }
    default:
        fmt.Fprintf(os.Stdout, "%s\n", obj)
    }
}

func main() {
    /* {"compact":true,"schema":0} in JSON */
    msgp := []byte{0x82, 0xa7, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x63, 0x74, 0xc3, 0xa6, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x00}

    ret, err := msgpack.Decode(bytes.NewBuffer(msgp))
    if err != nil {
        os.Exit(-1)
    }
    showMsgPack(ret)
}

出力結果は、下記のようになります。

fixstr(0xa7): val="compact"
true(0xc3): val=true
fixstr(0xa6): val="schema"
positive fixint(0x00): val=0

独自のExt Typeを定義する

MessagePackには、Ext Typeを用いることでユーザーが独自フォーマットを定義することができます。 例としては、FluentdのEventTimeがあります。

本ライブラリでは、下記のstruct埋め、RegisterExt関数に食わせることで、ユーザー定義のフォーマットを追加することができます。

type ExtFormat struct {
    FirstByte  byte  /* どのフォーマットかを示すbyte。0xd4-0xd8, 0xc7-0xc9のいずれか。 */
    ExtType    int8 /* 種別を示す。正の整数。負の値は予約されているため、通常は使わない。*/
    TypeName   string /* Type 名称 */
    DecodeFunc func([]byte) string /* []]byte列を変換するための関数 */
}

FluentdのEventTimeの場合の例は下記のようになります。

func extEventTimeV1(b []byte) string {
    if len(b) != 8 {
        return ""
    }
    var sec int32
    if binary.Read(bytes.NewReader(b[:4]), binary.BigEndian, &sec) != nil {
        return ""
    }
    var nsec int32
    if binary.Read(bytes.NewReader(b[4:]), binary.BigEndian, &nsec) != nil {
        return ""
    }
    return fmt.Sprintf("%v", time.Unix(int64(sec), int64(nsec)))
}

// RegisterFluentdEventTime registers Fluentd ext timestamp format.
// https://github.com/fluent/fluentd/wiki/Forward-Protocol-Specification-v1 */
func RegisterFluentdEventTime() {
    msgpack.RegisterExt(&msgpack.ExtFormat{FirstByte: msgpack.FixExt8Format, ExtType: 0, TypeName: "event time", DecodeFunc: extEventTimeV1})
    msgpack.RegisterExt(&msgpack.ExtFormat{FirstByte: msgpack.Ext8Format, ExtType: 0, TypeName: "event time", DecodeFunc: extEventTimeV1})
}

*1:ext typeの独自定義については、msgpack-inspectもrubyファイルの追加読み込みで対応可能。

*2:但し、明示的に有効化する必要あり。

*3:Length と異なるサイズの要素を含む配列など

カーネル空間からUEFI Runtime APIを触る

UEFI の Runtime serviceをLinuxカーネル空間からアクセスする方法について。 手元にあった Linux 4.1.27 で確認しています。

ざっくりまとめ

include/linux/efi.h に efiという構造体が定義されていて、そこにRuntime serviceの各APIの関数ポインタが集約されている。 各APIの仕様はUEFIの仕様書参照のこと。

Unicode変換やユーザー空間からのアクセスはどうやるんだろう?

http://www.uefi.org/specifications

include/linux/efi.h

efi 構造体

include/linux/efi.hに定義されている。メンバはRuntime serviceの各API。runtime_version 変数から実行環境のUEFI Versionを取れたりする。

extern struct efi {
    efi_system_table_t *systab; /* EFI system table */
    unsigned int runtime_version; /* Runtime services version */
    unsigned long mps;        /* MPS table */
    unsigned long acpi;       /* ACPI table  (IA64 ext 0.71) */
    unsigned long acpi20;     /* ACPI table  (ACPI 2.0) */
    unsigned long smbios;     /* SMBIOS table (32 bit entry point) */
    unsigned long smbios3;        /* SMBIOS table (64 bit entry point) */
    unsigned long sal_systab; /* SAL system table */
    unsigned long boot_info;  /* boot info table */
    unsigned long hcdp;       /* HCDP table */
    unsigned long uga;        /* UGA table */
    unsigned long uv_systab;  /* UV system table */
    unsigned long fw_vendor;  /* fw_vendor */
    unsigned long runtime;        /* runtime table */
    unsigned long config_table;   /* config tables */
    efi_get_time_t *get_time;
    efi_set_time_t *set_time;
    efi_get_wakeup_time_t *get_wakeup_time;
    efi_set_wakeup_time_t *set_wakeup_time;
    efi_get_variable_t *get_variable;
    efi_get_next_variable_t *get_next_variable;
    efi_set_variable_t *set_variable;
    efi_set_variable_nonblocking_t *set_variable_nonblocking;
    efi_query_variable_info_t *query_variable_info;
    efi_update_capsule_t *update_capsule;
    efi_query_capsule_caps_t *query_capsule_caps;
    efi_get_next_high_mono_count_t *get_next_high_mono_count;
    efi_reset_system_t *reset_system;
    efi_set_virtual_address_map_t *set_virtual_address_map;
    struct efi_memory_map *memmap;
    unsigned long flags;
} efi;

APIの実装はdrivers/firmware/efi/runtime-wrappers.c。下記のように各API内部で排他してくれている。

static efi_status_t virt_efi_set_variable(efi_char16_t *name,
                      efi_guid_t *vendor,
                      u32 attr,
                      unsigned long data_size,
                      void *data)
{
    unsigned long flags;
    efi_status_t status;

    spin_lock_irqsave(&efi_runtime_lock, flags);
    status = efi_call_virt(set_variable, name, vendor, attr, data_size,
                   data);
    spin_unlock_irqrestore(&efi_runtime_lock, flags);
    return status;
}

GUID変換

GUID変換は下記のマクロを使う。

#define EFI_GUID(a,b,c,d0,d1,d2,d3,d4,d5,d6,d7) \
((efi_guid_t) \
{{ (a) & 0xff, ((a) >> 8) & 0xff, ((a) >> 16) & 0xff, ((a) >> 24) & 0xff, \
  (b) & 0xff, ((b) >> 8) & 0xff, \
  (c) & 0xff, ((c) >> 8) & 0xff, \
  (d0), (d1), (d2), (d3), (d4), (d5), (d6), (d7) }})

同ヘッダ内で下記のように使用されている。

#define ACPI_TABLE_GUID    \
    EFI_GUID(  0xeb9d2d30, 0x2d88, 0x11d3, 0x9a, 0x16, 0x0, 0x90, 0x27, 0x3f, 0xc1, 0x4d )

文字列変換

UEFI使用上はUnicodeを使用する必要がある。カーネル内部的にはefi_char16_t。

typedef u16 efi_char16_t;       /* UNICODE character */

efi_char16_t から ascii への変換は、include/linux/ucs2_string.h の ucs2_as_utf8 を使えばいけそう。 ascii から efi_char16_t への変換はどうするのがよいのだろう?

ユーザー空間では

ユーザー空間からUEFI変数を操作する場合は、efivarfs 経由でいける。Rumtime serviceを触るにはどうすれば良いのだろう?