Как отобразить gzip-архиватор Protoc-gen-go FileDescriptorProto в виде открытого текста?

protoc-gen-go генерирует что-то вроде этого в конце сгенерированных файлов go:


var fileDescriptor_13c75530f718feb4 = []byte{
    // 2516 bytes of a gzipped FileDescriptorProto
    0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xbc, 0x59, 0xdf, 0x6f, 0x1c, 0x47,
...
}

Я хочу прочитать его открытым текстом для целей отладки. Как это сделать?

Почему мне это нужно - небольшое изменение, которое не должно приводить к изменению этого сгенерированного файла делает, и я выясняю, почему (и это трудно отлаживать, поскольку это просто двоичный blob).


person Karel Bílek    schedule 05.03.2020    source источник


Ответы (2)


Я написал такой код, чтобы разобрать и распечатать каплю.

Ключевая логика фактически взята из

package main

import (
    "bytes"
    "compress/gzip"
    "encoding/json"
    "fmt"

    "io/ioutil"

    proto "github.com/golang/protobuf/proto"
    dpb "github.com/golang/protobuf/protoc-gen-go/descriptor"
    _ [here write path to your generated go source]
    // include the line above if you want to use proto.FileDescriptor,
    // leave if you just copy-paste the bytes below
)

func main() {
    // here write the path that is used in the generated file
    // in init(), as an argument to proto.RegisterFile 
    // (or just copypaste the bytes instead of using proto.FileDescriptor)
    bytes := proto.FileDescriptor(XXX)

    fd, err := decodeFileDesc(bytes)
    if err != nil {
        panic(err)
    }
    b, err := json.MarshalIndent(fd,"","  ")
    if err != nil {
        panic(err)
    }
    fmt.Println(string(b))
}

// decompress does gzip decompression.
func decompress(b []byte) ([]byte, error) {
    r, err := gzip.NewReader(bytes.NewReader(b))
    if err != nil {
        return nil, fmt.Errorf("bad gzipped descriptor: %v", err)
    }
    out, err := ioutil.ReadAll(r)
    if err != nil {
        return nil, fmt.Errorf("bad gzipped descriptor: %v", err)
    }
    return out, nil
}

func decodeFileDesc(enc []byte) (*dpb.FileDescriptorProto, error) {
    raw, err := decompress(enc)
    if err != nil {
        return nil, fmt.Errorf("failed to decompress enc: %v", err)
    }

    fd := new(dpb.FileDescriptorProto)
    if err := proto.Unmarshal(raw, fd); err != nil {
        return nil, fmt.Errorf("bad descriptor: %v", err)
    }
    return fd, nil
}

Это печатает данные из прото-файла в виде JSON.

Как отмечает Марк Гравелл в комментарии к другому ответу, сжатие gzip не является детерминированным, поэтому один и тот же прото-файл может создавать разные сжатые с помощью gzip FileDescriptorProto на двух разных компьютерах.

person Karel Bílek    schedule 05.03.2020

FileDescriptorProto не является обычным текстом; он не содержит исходную схему как текст, а скорее: это экземпляр FileDescriptorProto в двоичной кодировке protobuf, как определено в _ 3_, содержащий обработанное значение исходной схемы.

Так; вы можете десериализовать эту полезную нагрузку (после разархивирования) как FileDescriptorProto и использовать любой API отражения / метаданных, доступный в "go", чтобы получить это в некоторой текстовой форме. Если реализация protobuf на go включает API protobuf json (а не двоичный), вы можете просто вызвать API write-json для экземпляра FileDescriptorProto. Примечание: не все реализации protobuf реализуют json API.

person Marc Gravell    schedule 05.03.2020
comment
Ох. Тогда я постараюсь уточнить исходный вопрос. Небольшое изменение, которое не должно приводить к изменению этого сгенерированного файла, делает, и я выясняю, почему (и это сложно отлаживать). - person Karel Bílek; 05.03.2020
comment
@ KarelBílek хорошо, у вас есть пример этого небольшого изменения, чтобы я мог высказать свое мнение о том, следует ли / не следует ли изменять дескриптор? также: обратите внимание, что gzip не обязательно должен быть детерминированным - все, что требуется, это чтобы после распаковки вы получали те же данные, с которых вы начали; может быть несколько способов выбора стратегии сжатия, и бросок кости может быть совершенно правильной стратегией для выбора в некоторых случаях! (по памяти, когда задействован параллелизм, это также может зависеть от того, в каком порядке обновляются таблицы состояний); т.е. блоки A- ›B-› C против A- ›C-› B - person Marc Gravell; 05.03.2020
comment
Вы правы, повторный синтаксический анализ кода показывает точно такие же результаты, так что gzip просто недетерминирован. В качестве ответа напишу сам код. - person Karel Bílek; 05.03.2020