From 0c9be48a90bfafac68cde05c4d7db8eee17492f6 Mon Sep 17 00:00:00 2001 From: Ian Lance Taylor Date: Tue, 19 Jun 2018 16:53:18 -0700 Subject: [PATCH] go/internal/gccgoimporter: read export data from archives When used with the go tool, gccgo will normally generate archive files. This change teaches the gccgoimporter package how to read the export data from an archive. This is needed by, for example, cmd/vet, when typechecking packages. Change-Id: I21267949a7808cd81c0042af425c774a4ff7d82f Reviewed-on: https://go-review.googlesource.com/119895 Run-TryBot: Ian Lance Taylor TryBot-Result: Gobot Gobot Reviewed-by: Robert Griesemer --- src/go/internal/gccgoimporter/ar.go | 148 ++++++++++++++++++ src/go/internal/gccgoimporter/importer.go | 52 +++--- .../internal/gccgoimporter/importer_test.go | 1 + .../gccgoimporter/testdata/libimportsar.a | Bin 0 -> 9302 bytes 4 files changed, 179 insertions(+), 22 deletions(-) create mode 100644 src/go/internal/gccgoimporter/ar.go create mode 100644 src/go/internal/gccgoimporter/testdata/libimportsar.a diff --git a/src/go/internal/gccgoimporter/ar.go b/src/go/internal/gccgoimporter/ar.go new file mode 100644 index 00000000000..ebd08b8f359 --- /dev/null +++ b/src/go/internal/gccgoimporter/ar.go @@ -0,0 +1,148 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package gccgoimporter + +import ( + "bytes" + "debug/elf" + "errors" + "fmt" + "io" + "strconv" + "strings" +) + +// Magic strings for different archive file formats. +const ( + armag = "!\n" + armagt = "!\n" + armagb = "\n" +) + +// Offsets and sizes for fields in a standard archive header. +const ( + arNameOff = 0 + arNameSize = 16 + arDateOff = arNameOff + arNameSize + arDateSize = 12 + arUIDOff = arDateOff + arDateSize + arUIDSize = 6 + arGIDOff = arUIDOff + arUIDSize + arGIDSize = 6 + arModeOff = arGIDOff + arGIDSize + arModeSize = 8 + arSizeOff = arModeOff + arModeSize + arSizeSize = 10 + arFmagOff = arSizeOff + arSizeSize + arFmagSize = 2 + + arHdrSize = arFmagOff + arFmagSize +) + +// The contents of the fmag field of a standard archive header. +const arfmag = "`\n" + +// arExportData takes an archive file and returns a ReadSeeker for the +// export data in that file. This assumes that there is only one +// object in the archive containing export data, which is not quite +// what gccgo does; gccgo concatenates together all the export data +// for all the objects in the file. In practice that case does not arise. +func arExportData(archive io.ReadSeeker) (io.ReadSeeker, error) { + if _, err := archive.Seek(0, io.SeekStart); err != nil { + return nil, err + } + + var buf [len(armag)]byte + if _, err := archive.Read(buf[:]); err != nil { + return nil, err + } + + switch string(buf[:]) { + case armag: + return standardArExportData(archive) + case armagt: + return nil, errors.New("unsupported thin archive") + case armagb: + return nil, errors.New("unsupported AIX big archive") + default: + return nil, fmt.Errorf("unrecognized archive file format %q", buf[:]) + } +} + +// standardArExportData returns export data form a standard archive. +func standardArExportData(archive io.ReadSeeker) (io.ReadSeeker, error) { + off := int64(len(armag)) + for { + var hdrBuf [arHdrSize]byte + if _, err := archive.Read(hdrBuf[:]); err != nil { + return nil, err + } + off += arHdrSize + + if bytes.Compare(hdrBuf[arFmagOff:arFmagOff+arFmagSize], []byte(arfmag)) != 0 { + return nil, fmt.Errorf("archive header format header (%q)", hdrBuf[:]) + } + + size, err := strconv.ParseInt(strings.TrimSpace(string(hdrBuf[arSizeOff:arSizeOff+arSizeSize])), 10, 64) + if err != nil { + return nil, fmt.Errorf("error parsing size in archive header (%q): %v", hdrBuf[:], err) + } + + fn := hdrBuf[arNameOff : arNameOff+arNameSize] + if fn[0] == '/' && (fn[1] == ' ' || fn[1] == '/' || bytes.Compare(fn[:8], []byte("/SYM64/ ")) == 0) { + // Archive symbol table or extended name table, + // which we don't care about. + } else { + archiveAt := readerAtFromSeeker(archive) + ret, err := elfFromAr(io.NewSectionReader(archiveAt, off, size)) + if ret != nil || err != nil { + return ret, err + } + } + + if size&1 != 0 { + size++ + } + off += size + if _, err := archive.Seek(off, io.SeekStart); err != nil { + return nil, err + } + } +} + +// elfFromAr tries to get export data from an archive member as an ELF file. +// If there is no export data, this returns nil, nil. +func elfFromAr(member *io.SectionReader) (io.ReadSeeker, error) { + ef, err := elf.NewFile(member) + if err != nil { + return nil, err + } + sec := ef.Section(".go_export") + if sec == nil { + return nil, nil + } + return sec.Open(), nil +} + +// readerAtFromSeeker turns an io.ReadSeeker into an io.ReaderAt. +// This is only safe because there won't be any concurrent seeks +// while this code is executing. +func readerAtFromSeeker(rs io.ReadSeeker) io.ReaderAt { + if ret, ok := rs.(io.ReaderAt); ok { + return ret + } + return seekerReadAt{rs} +} + +type seekerReadAt struct { + seeker io.ReadSeeker +} + +func (sra seekerReadAt) ReadAt(p []byte, off int64) (int, error) { + if _, err := sra.seeker.Seek(off, io.SeekStart); err != nil { + return 0, err + } + return sra.seeker.Read(p) +} diff --git a/src/go/internal/gccgoimporter/importer.go b/src/go/internal/gccgoimporter/importer.go index d4998cf2a2e..159cc50719f 100644 --- a/src/go/internal/gccgoimporter/importer.go +++ b/src/go/internal/gccgoimporter/importer.go @@ -6,13 +6,11 @@ package gccgoimporter // import "go/internal/gccgoimporter" import ( - "bytes" "debug/elf" "fmt" "go/types" "io" "os" - "os/exec" "path/filepath" "strings" ) @@ -98,18 +96,8 @@ func openExportFile(fpath string) (reader io.ReadSeeker, closer io.Closer, err e return case archiveMagic: - // TODO(pcc): Read the archive directly instead of using "ar". - f.Close() - closer = nil - - cmd := exec.Command("ar", "p", fpath) - var out []byte - out, err = cmd.Output() - if err != nil { - return - } - - elfreader = bytes.NewReader(out) + reader, err = arExportData(f) + return default: elfreader = f @@ -189,17 +177,24 @@ func GetImporter(searchpaths []string, initmap map[*types.Package]InitData) Impo reader = r } - var magic [4]byte - _, err = reader.Read(magic[:]) - if err != nil { - return - } - _, err = reader.Seek(0, io.SeekStart) + var magics string + magics, err = readMagic(reader) if err != nil { return } - switch string(magic[:]) { + if magics == archiveMagic { + reader, err = arExportData(reader) + if err != nil { + return + } + magics, err = readMagic(reader) + if err != nil { + return + } + } + + switch magics { case gccgov1Magic, gccgov2Magic: var p parser p.init(fpath, reader, imports) @@ -230,9 +225,22 @@ func GetImporter(searchpaths []string, initmap map[*types.Package]InitData) Impo // } default: - err = fmt.Errorf("unrecognized magic string: %q", string(magic[:])) + err = fmt.Errorf("unrecognized magic string: %q", magics) } return } } + +// readMagic reads the four bytes at the start of a ReadSeeker and +// returns them as a string. +func readMagic(reader io.ReadSeeker) (string, error) { + var magic [4]byte + if _, err := reader.Read(magic[:]); err != nil { + return "", err + } + if _, err := reader.Seek(0, io.SeekStart); err != nil { + return "", err + } + return string(magic[:]), nil +} diff --git a/src/go/internal/gccgoimporter/importer_test.go b/src/go/internal/gccgoimporter/importer_test.go index 01ab47a4451..5a699687bd4 100644 --- a/src/go/internal/gccgoimporter/importer_test.go +++ b/src/go/internal/gccgoimporter/importer_test.go @@ -101,6 +101,7 @@ var importerTests = [...]importerTest{ {pkgpath: "unicode", name: "IsUpper", want: "func IsUpper(r rune) bool"}, {pkgpath: "unicode", name: "MaxRune", want: "const MaxRune untyped rune", wantval: "1114111"}, {pkgpath: "imports", wantinits: []string{"imports..import", "fmt..import", "math..import"}}, + {pkgpath: "importsar", name: "Hello", want: "var Hello string"}, {pkgpath: "alias", name: "IntAlias2", want: "type IntAlias2 = Int"}, {pkgpath: "escapeinfo", name: "NewT", want: "func NewT(data []byte) *T"}, } diff --git a/src/go/internal/gccgoimporter/testdata/libimportsar.a b/src/go/internal/gccgoimporter/testdata/libimportsar.a new file mode 100644 index 0000000000000000000000000000000000000000..6f30758151dbc3ebbf037c54b55f4e84fb055277 GIT binary patch literal 9302 zcmb_iU5p!76~6ZDCeCiN%}*%_r7#HzB%wQN?|OIbZj^d=o5U4q8Aoy96D~k)& zpyfCVWf813UB_`Ut-!A~Dk2n)vm~nZrsqSc=+1Aq4WOlCP4S z{h4fkegnj@&g;iI|2Wq9^Rdofi=DTNomb%~b*=sCwf5C(?JLF3E5**QWT@hWU)sga z;pdB;Q!iYkZ4ggfX=kF|*5XCXT|D(dv11iGCsS`8C|)Avmx`U&x_P~MQyf~np-5y<86@??z|$%uuya4OoYzqc)j! zqjA%#)$D2`aQ#NPR-#cf6}YXS=2g103WyY{o?Z2#k=L?4oIjP5?=IKe#lTjwi?cO$}*gNHsG7t8p;NEO-(;1)c`y zz&E`20FKariBoSw?f3?)=yI-sO< zdYk=-=hv3#p3-Q8)~BvHHm2Y6aHFzF8S?wp?_HWoG5Vg}_84q2{C{)#k5BISKl4z0E2Giaq{Qd9|7eGlCflt4+bfOaq;S8qd# z3^wAH2L34FDOoq=WMXh|6woFMjU;*mh=leW!H_daJR=fY6MILt4_hO*jBHJ8+qeZ! zhUCq{8o7OVX85k{$ZmULcsPO1z~%_e4I}0rF+6+-b~r#cqD#6PVVGjWR95NdF?Yw# z}_$?qNqh5%Ur5rik@TH=S zoz4j~Q(1BJo>TTw&(1q(2c;~X$)%^#llDG$wdwjO!;PR^+ppeEA}{QX(-BAK=Wn<7 z!PWkEa3q(9UoR4mo)C%EEs0xh*qFMERosKw4@(VopdK38@|EQL<_&jUvYr~aFqnJ^ zifTSi(0&bwx39&`S?W$Q=d3=}$`V|4)>~M{apGA^$lHR4k+cN84EB;GaO4f5*PMig zb6p@cOKdi@g}@C0{Vu*<|HdBu4@v!AoD?mgTZg8*q<$CQ7s9fQddc4JhG2S(`zdcyx;L%G5|->sFMz+DZ5Bj4Mdg;q=8jk|;wX#9*P9N#0{(W<|KFh#aJ zA3jHVv2j5z;StZ7i5Qz~3^utQGk$7Z7X#OUpC9RW#}E#arC@2{G`zrd@K!(CYSya3 zX!x8^dhSBIf**mFJzTi#mlqa%_Z+(kA-~+HxL6N)rJ-7_+Hlzf!G*Kv)#)=sk0zf- zp^>{%TK3Czm{feP&3BgCsP`$Jz{c~vsk(&zHxxAsQAbA;ON`N_(vGa z_=bH-CAzBg>H6vY+>HNQU~%#7!O*Z6im0`tUf6jBA};=U75}o*r}*^ifEoWyU=-gx zKj&5aH0E@&^#S^?gX7kJi+U)qJ;kRzH{<^c7{xc~c@^KpP2KGPOpI*U{wIb5Q+EVF zT>b@>|I^BV@~2vF=KoR5n?5PzvU;F=l?ED_y5oK9-%|Fxe(b`u9|aJX|L@gLN^DR0 z)2}gR{zv-6zjb36@ktJ>q#1v{ zu#f#`RiFd!V85*F4K;Cr6E!9{F2j#0d*k*OPb&KYD=KNaJqP=^{j+1%Z!dhXJ+CX} zW5%XXSC!&6cdX-UlR#^soW#F)r8`%6Gpw`<%-EDZ{`RiW2?Wzf@h~ zzr(-N-{3Rp%#S5?6G+$OP9uisU=r?%!DFBgv;nY;h~yT?zAojyEw zdTrv)puOs6?{ymw=e$!*X>m>B6+*}#ydZV~e)Mkeppo^0bzC7<)>$Qg*%h{`ZYBCa|8 cYQ_$gl$o13rbnx}_T+!3QhbO88kzS01LaMjO#lD@ literal 0 HcmV?d00001