cmd/link: support PIE internal linking on darwin/amd64

This CL adds support of PIE internal linking on darwin/amd64.

This is also preparation for supporting internal linking on
darwin/arm64 (macOS), which requires PIE for everything.

Updates #38485.

Change-Id: I2ed58583dcc102f5e0521982491fc7ba6f2754ed
Reviewed-on: https://go-review.googlesource.com/c/go/+/261642
Trust: Cherry Zhang <cherryyz@google.com>
Reviewed-by: Than McIntosh <thanm@google.com>
This commit is contained in:
Cherry Zhang 2020-10-11 15:48:22 -04:00
parent c8eea1633e
commit f46a5b1e45
10 changed files with 289 additions and 54 deletions

View file

@ -3,6 +3,7 @@
// license that can be found in the LICENSE file.
// +build !windows,!static
// +build !darwin !internal_pie
#include <stdint.h>
#include <dlfcn.h>

View file

@ -3,6 +3,10 @@
// license that can be found in the LICENSE file.
// +build !windows,!static
// +build !darwin !internal_pie
// Excluded in darwin internal linking PIE mode, as dynamic export is not
// supported.
package cgotest

View file

@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build windows static
// +build windows static darwin,internal_pie
package cgotest

17
src/cmd/dist/test.go vendored
View file

@ -964,10 +964,10 @@ func (t *tester) internalLink() bool {
func (t *tester) internalLinkPIE() bool {
switch goos + "-" + goarch {
case "linux-amd64", "linux-arm64",
"android-arm64":
return true
case "windows-amd64", "windows-386", "windows-arm":
case "darwin-amd64",
"linux-amd64", "linux-arm64",
"android-arm64",
"windows-amd64", "windows-386", "windows-arm":
return true
}
return false
@ -1100,6 +1100,13 @@ func (t *tester) cgoTest(dt *distTest) error {
cmd = t.addCmd(dt, "misc/cgo/test", t.goTest(), "-ldflags", "-linkmode=external -s")
if t.supportedBuildmode("pie") {
t.addCmd(dt, "misc/cgo/test", t.goTest(), "-buildmode=pie")
if t.internalLink() && t.internalLinkPIE() {
t.addCmd(dt, "misc/cgo/test", t.goTest(), "-buildmode=pie", "-ldflags=-linkmode=internal", "-tags=internal,internal_pie")
}
}
case "aix-ppc64",
"android-arm", "android-arm64",
"dragonfly-amd64",
@ -1151,7 +1158,7 @@ func (t *tester) cgoTest(dt *distTest) error {
if t.supportedBuildmode("pie") {
t.addCmd(dt, "misc/cgo/test", t.goTest(), "-buildmode=pie")
if t.internalLink() && t.internalLinkPIE() {
t.addCmd(dt, "misc/cgo/test", t.goTest(), "-buildmode=pie", "-ldflags=-linkmode=internal")
t.addCmd(dt, "misc/cgo/test", t.goTest(), "-buildmode=pie", "-ldflags=-linkmode=internal", "-tags=internal,internal_pie")
}
t.addCmd(dt, "misc/cgo/testtls", t.goTest(), "-buildmode=pie")
t.addCmd(dt, "misc/cgo/nocgo", t.goTest(), "-buildmode=pie")

View file

@ -118,7 +118,8 @@ func BuildModeSupported(compiler, buildmode, goos, goarch string) bool {
func InternalLinkPIESupported(goos, goarch string) bool {
switch goos + "/" + goarch {
case "linux/amd64", "linux/arm64",
case "darwin/amd64",
"linux/amd64", "linux/arm64",
"android/arm64",
"windows-amd64", "windows-386", "windows-arm":
return true

View file

@ -76,9 +76,9 @@ func adddynrel(target *ld.Target, ldr *loader.Loader, syms *ld.ArchSyms, s loade
targType = ldr.SymType(targ)
}
switch r.Type() {
switch rt := r.Type(); rt {
default:
if r.Type() >= objabi.ElfRelocOffset {
if rt >= objabi.ElfRelocOffset {
ldr.Errorf(s, "unexpected relocation type %d (%s)", r.Type(), sym.RelocName(target.Arch, r.Type()))
return false
}
@ -167,13 +167,24 @@ func adddynrel(target *ld.Target, ldr *loader.Loader, syms *ld.ArchSyms, s loade
case objabi.MachoRelocOffset + ld.MACHO_X86_64_RELOC_UNSIGNED*2 + 0,
objabi.MachoRelocOffset + ld.MACHO_X86_64_RELOC_SIGNED*2 + 0,
objabi.MachoRelocOffset + ld.MACHO_X86_64_RELOC_BRANCH*2 + 0:
// TODO: What is the difference between all these?
su := ldr.MakeSymbolUpdater(s)
su.SetRelocType(rIdx, objabi.R_ADDR)
if targType == sym.SDYNIMPORT {
ldr.Errorf(s, "unexpected reloc for dynamic symbol %s", ldr.SymName(targ))
}
if target.IsPIE() && target.IsInternal() {
// For internal linking PIE, this R_ADDR relocation cannot
// be resolved statically. We need to generate a dynamic
// relocation. Let the code below handle it.
if rt == objabi.MachoRelocOffset+ld.MACHO_X86_64_RELOC_UNSIGNED*2 {
break
} else {
// MACHO_X86_64_RELOC_SIGNED or MACHO_X86_64_RELOC_BRANCH
// Can this happen? The object is expected to be PIC.
ldr.Errorf(s, "unsupported relocation for PIE: %v", rt)
}
}
return true
case objabi.MachoRelocOffset + ld.MACHO_X86_64_RELOC_BRANCH*2 + 1:
@ -223,7 +234,7 @@ func adddynrel(target *ld.Target, ldr *loader.Loader, syms *ld.ArchSyms, s loade
if targType != sym.SDYNIMPORT {
ldr.Errorf(s, "unexpected GOT reloc for non-dynamic symbol %s", ldr.SymName(targ))
}
ld.AddGotSym(target, ldr, syms, targ, uint32(elf.R_X86_64_GLOB_DAT))
ld.AddGotSym(target, ldr, syms, targ, 0)
su := ldr.MakeSymbolUpdater(s)
su.SetRelocType(rIdx, objabi.R_PCREL)
su.SetRelocSym(rIdx, syms.GOT)
@ -355,28 +366,15 @@ func adddynrel(target *ld.Target, ldr *loader.Loader, syms *ld.ArchSyms, s loade
return true
}
if target.IsDarwin() && ldr.SymSize(s) == int64(target.Arch.PtrSize) && r.Off() == 0 {
if target.IsDarwin() {
// Mach-O relocations are a royal pain to lay out.
// They use a compact stateful bytecode representation
// that is too much bother to deal with.
// Instead, interpret the C declaration
// void *_Cvar_stderr = &stderr;
// as making _Cvar_stderr the name of a GOT entry
// for stderr. This is separate from the usual GOT entry,
// just in case the C code assigns to the variable,
// and of course it only works for single pointers,
// but we only need to support cgo and that's all it needs.
ld.Adddynsym(ldr, target, syms, targ)
got := ldr.MakeSymbolUpdater(syms.GOT)
su := ldr.MakeSymbolUpdater(s)
su.SetType(got.Type())
got.AddInteriorSym(s)
su.SetValue(got.Size())
got.AddUint64(target.Arch, 0)
leg := ldr.MakeSymbolUpdater(syms.LinkEditGOT)
leg.AddUint32(target.Arch, uint32(ldr.SymDynid(targ)))
su.SetRelocType(rIdx, objabi.ElfRelocOffset) // ignore during relocsym
// They use a compact stateful bytecode representation.
// Here we record what are needed and encode them later.
ld.MachoAddRebase(s, int64(r.Off()))
// Not mark r done here. So we still apply it statically,
// so in the file content we'll also have the right offset
// to the relocation target. So it can be examined statically
// (e.g. go version).
return true
}
}
@ -627,26 +625,16 @@ func addpltsym(target *ld.Target, ldr *loader.Loader, syms *ld.ArchSyms, s loade
ldr.SetPlt(s, int32(plt.Size()-16))
} else if target.IsDarwin() {
// To do lazy symbol lookup right, we're supposed
// to tell the dynamic loader which library each
// symbol comes from and format the link info
// section just so. I'm too lazy (ha!) to do that
// so for now we'll just use non-lazy pointers,
// which don't need to be told which library to use.
//
// https://networkpx.blogspot.com/2009/09/about-lcdyldinfoonly-command.html
// has details about what we're avoiding.
ld.AddGotSym(target, ldr, syms, s, uint32(elf.R_X86_64_GLOB_DAT))
plt := ldr.MakeSymbolUpdater(syms.PLT)
ld.AddGotSym(target, ldr, syms, s, 0)
sDynid := ldr.SymDynid(s)
lep := ldr.MakeSymbolUpdater(syms.LinkEditPLT)
lep.AddUint32(target.Arch, uint32(sDynid))
// jmpq *got+size(IP)
plt := ldr.MakeSymbolUpdater(syms.PLT)
ldr.SetPlt(s, int32(plt.Size()))
// jmpq *got+size(IP)
plt.AddUint8(0xff)
plt.AddUint8(0x25)
plt.AddPCRelPlus(target.Arch, syms.GOT, int64(ldr.SymGot(s)))
@ -654,6 +642,7 @@ func addpltsym(target *ld.Target, ldr *loader.Loader, syms *ld.ArchSyms, s loade
ldr.Errorf(s, "addpltsym: unsupported binary format")
}
}
func tlsIEtoLE(P []byte, off, size int) {
// Transform the PC-relative instruction into a constant load.
// That is,

View file

@ -222,6 +222,7 @@ func mustLinkExternal(ctxt *Link) (res bool, reason string) {
switch objabi.GOOS + "/" + objabi.GOARCH {
case "linux/amd64", "linux/arm64", "android/arm64":
case "windows/386", "windows/amd64", "windows/arm":
case "darwin/amd64":
default:
// Internal linking does not support TLS_IE.
return true, "buildmode=pie"

View file

@ -2526,6 +2526,12 @@ func AddGotSym(target *Target, ldr *loader.Loader, syms *ArchSyms, s loader.Sym,
} else if target.IsDarwin() {
leg := ldr.MakeSymbolUpdater(syms.LinkEditGOT)
leg.AddUint32(target.Arch, uint32(ldr.SymDynid(s)))
if target.IsPIE() && target.IsInternal() {
// Mach-O relocations are a royal pain to lay out.
// They use a compact stateful bytecode representation.
// Here we record what are needed and encode them later.
MachoAddBind(int64(ldr.SymGot(s)), s)
}
} else {
ldr.Errorf(s, "addgotsym: unsupported binary format")
}

View file

@ -118,6 +118,8 @@ const (
MH_EXECUTE = 0x2
MH_NOUNDEFS = 0x1
MH_DYLDLINK = 0x4
MH_PIE = 0x200000
)
const (
@ -193,6 +195,56 @@ const (
PLATFORM_BRIDGEOS MachoPlatform = 5
)
// rebase table opcode
const (
REBASE_TYPE_POINTER = 1
REBASE_TYPE_TEXT_ABSOLUTE32 = 2
REBASE_TYPE_TEXT_PCREL32 = 3
REBASE_OPCODE_MASK = 0xF0
REBASE_IMMEDIATE_MASK = 0x0F
REBASE_OPCODE_DONE = 0x00
REBASE_OPCODE_SET_TYPE_IMM = 0x10
REBASE_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB = 0x20
REBASE_OPCODE_ADD_ADDR_ULEB = 0x30
REBASE_OPCODE_ADD_ADDR_IMM_SCALED = 0x40
REBASE_OPCODE_DO_REBASE_IMM_TIMES = 0x50
REBASE_OPCODE_DO_REBASE_ULEB_TIMES = 0x60
REBASE_OPCODE_DO_REBASE_ADD_ADDR_ULEB = 0x70
REBASE_OPCODE_DO_REBASE_ULEB_TIMES_SKIPPING_ULEB = 0x80
)
// bind table opcode
const (
BIND_TYPE_POINTER = 1
BIND_TYPE_TEXT_ABSOLUTE32 = 2
BIND_TYPE_TEXT_PCREL32 = 3
BIND_SPECIAL_DYLIB_SELF = 0
BIND_SPECIAL_DYLIB_MAIN_EXECUTABLE = -1
BIND_SPECIAL_DYLIB_FLAT_LOOKUP = -2
BIND_SPECIAL_DYLIB_WEAK_LOOKUP = -3
BIND_OPCODE_MASK = 0xF0
BIND_IMMEDIATE_MASK = 0x0F
BIND_OPCODE_DONE = 0x00
BIND_OPCODE_SET_DYLIB_ORDINAL_IMM = 0x10
BIND_OPCODE_SET_DYLIB_ORDINAL_ULEB = 0x20
BIND_OPCODE_SET_DYLIB_SPECIAL_IMM = 0x30
BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM = 0x40
BIND_OPCODE_SET_TYPE_IMM = 0x50
BIND_OPCODE_SET_ADDEND_SLEB = 0x60
BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB = 0x70
BIND_OPCODE_ADD_ADDR_ULEB = 0x80
BIND_OPCODE_DO_BIND = 0x90
BIND_OPCODE_DO_BIND_ADD_ADDR_ULEB = 0xA0
BIND_OPCODE_DO_BIND_ADD_ADDR_IMM_SCALED = 0xB0
BIND_OPCODE_DO_BIND_ULEB_TIMES_SKIPPING_ULEB = 0xC0
BIND_OPCODE_THREADED = 0xD0
BIND_SUBOPCODE_THREADED_SET_BIND_ORDINAL_TABLE_SIZE_ULEB = 0x00
BIND_SUBOPCODE_THREADED_APPLY = 0x01
)
// Mach-O file writing
// https://developer.apple.com/mac/library/DOCUMENTATION/DeveloperTools/Conceptual/MachORuntime/Reference/reference.html
@ -279,7 +331,7 @@ var dylib []string
var linkoff int64
func machowrite(arch *sys.Arch, out *OutBuf, linkmode LinkMode) int {
func machowrite(ctxt *Link, arch *sys.Arch, out *OutBuf, linkmode LinkMode) int {
o1 := out.Offset()
loadsize := 4 * 4 * ndebug
@ -308,11 +360,14 @@ func machowrite(arch *sys.Arch, out *OutBuf, linkmode LinkMode) int {
}
out.Write32(uint32(len(load)) + uint32(nseg) + uint32(ndebug))
out.Write32(uint32(loadsize))
flags := uint32(0)
if nkind[SymKindUndef] == 0 {
out.Write32(MH_NOUNDEFS) /* flags - no undefines */
} else {
out.Write32(0) /* flags */
flags |= MH_NOUNDEFS
}
if ctxt.IsPIE() && linkmode == LinkInternal {
flags |= MH_PIE | MH_DYLDLINK
}
out.Write32(flags) /* flags */
if arch.PtrSize == 8 {
out.Write32(0) /* reserved */
}
@ -712,15 +767,17 @@ func asmbMacho(ctxt *Link) {
s2 := ldr.SymSize(ctxt.ArchSyms.LinkEditPLT)
s3 := ldr.SymSize(ctxt.ArchSyms.LinkEditGOT)
s4 := ldr.SymSize(ldr.Lookup(".machosymstr", 0))
s5 := ldr.SymSize(ldr.Lookup(".machorebase", 0))
s6 := ldr.SymSize(ldr.Lookup(".machobind", 0))
if ctxt.LinkMode != LinkExternal {
ms := newMachoSeg("__LINKEDIT", 0)
ms.vaddr = uint64(Rnd(int64(Segdata.Vaddr+Segdata.Length), int64(*FlagRound)))
ms.vsize = uint64(s1) + uint64(s2) + uint64(s3) + uint64(s4)
ms.vsize = uint64(s1 + s2 + s3 + s4 + s5 + s6)
ms.fileoffset = uint64(linkoff)
ms.filesize = ms.vsize
ms.prot1 = 7
ms.prot2 = 3
ms.prot1 = 1
ms.prot2 = 1
}
ml := newMachoLoad(ctxt.Arch, LC_SYMTAB, 4)
@ -745,9 +802,23 @@ func asmbMacho(ctxt *Link) {
stringtouint32(ml.data[4:], lib)
}
}
if ctxt.LinkMode != LinkExternal && ctxt.IsPIE() {
ml := newMachoLoad(ctxt.Arch, LC_DYLD_INFO_ONLY, 10)
ml.data[0] = uint32(linkoff + s1 + s2 + s3 + s4) // rebase off
ml.data[1] = uint32(s5) // rebase size
ml.data[2] = uint32(linkoff + s1 + s2 + s3 + s4 + s5) // bind off
ml.data[3] = uint32(s6) // bind size
ml.data[4] = 0 // weak bind off
ml.data[5] = 0 // weak bind size
ml.data[6] = 0 // lazy bind off
ml.data[7] = 0 // lazy bind size
ml.data[8] = 0 // export
ml.data[9] = 0 // export size
}
}
a := machowrite(ctxt.Arch, ctxt.Out, ctxt.LinkMode)
a := machowrite(ctxt, ctxt.Arch, ctxt.Out, ctxt.LinkMode)
if int32(a) > HEADR {
Exitf("HEADR too small: %d > %d", a, HEADR)
}
@ -989,6 +1060,8 @@ func machodysymtab(ctxt *Link) {
func doMachoLink(ctxt *Link) int64 {
machosymtab(ctxt)
machoDyldInfo(ctxt)
ldr := ctxt.loader
// write data that will be linkedit section
@ -996,6 +1069,8 @@ func doMachoLink(ctxt *Link) int64 {
s2 := ctxt.ArchSyms.LinkEditPLT
s3 := ctxt.ArchSyms.LinkEditGOT
s4 := ldr.Lookup(".machosymstr", 0)
s5 := ldr.Lookup(".machorebase", 0)
s6 := ldr.Lookup(".machobind", 0)
// Force the linkedit section to end on a 16-byte
// boundary. This allows pure (non-cgo) Go binaries
@ -1019,7 +1094,7 @@ func doMachoLink(ctxt *Link) int64 {
s4b.AddUint8(0)
}
size := int(ldr.SymSize(s1) + ldr.SymSize(s2) + ldr.SymSize(s3) + ldr.SymSize(s4))
size := int(ldr.SymSize(s1) + ldr.SymSize(s2) + ldr.SymSize(s3) + ldr.SymSize(s4) + ldr.SymSize(s5) + ldr.SymSize(s6))
if size > 0 {
linkoff = Rnd(int64(uint64(HEADR)+Segtext.Length), int64(*FlagRound)) + Rnd(int64(Segrelrodata.Filelen), int64(*FlagRound)) + Rnd(int64(Segdata.Filelen), int64(*FlagRound)) + Rnd(int64(Segdwarf.Filelen), int64(*FlagRound))
@ -1029,6 +1104,8 @@ func doMachoLink(ctxt *Link) int64 {
ctxt.Out.Write(ldr.Data(s2))
ctxt.Out.Write(ldr.Data(s3))
ctxt.Out.Write(ldr.Data(s4))
ctxt.Out.Write(ldr.Data(s5))
ctxt.Out.Write(ldr.Data(s6))
}
return Rnd(int64(size), int64(*FlagRound))
@ -1172,3 +1249,134 @@ func peekMachoPlatform(m *macho.File) (*MachoPlatformLoad, error) {
}
return nil, nil
}
// A rebase entry tells the dynamic linker the data at sym+off needs to be
// relocated when the in-memory image moves. (This is somewhat like, say,
// ELF R_X86_64_RELATIVE).
// For now, the only kind of entry we support is that the data is an absolute
// address. That seems all we need.
// In the binary it uses a compact stateful bytecode encoding. So we record
// entries as we go and build the table at the end.
type machoRebaseRecord struct {
sym loader.Sym
off int64
}
var machorebase []machoRebaseRecord
func MachoAddRebase(s loader.Sym, off int64) {
machorebase = append(machorebase, machoRebaseRecord{s, off})
}
// A bind entry tells the dynamic linker the data at GOT+off should be bound
// to the address of the target symbol, which is a dynamic import.
// For now, the only kind of entry we support is that the data is an absolute
// address, and the source symbol is always the GOT. That seems all we need.
// In the binary it uses a compact stateful bytecode encoding. So we record
// entries as we go and build the table at the end.
type machoBindRecord struct {
off int64
targ loader.Sym
}
var machobind []machoBindRecord
func MachoAddBind(off int64, targ loader.Sym) {
machobind = append(machobind, machoBindRecord{off, targ})
}
// Generate data for the dynamic linker, used in LC_DYLD_INFO_ONLY load command.
// See mach-o/loader.h, struct dyld_info_command, for the encoding.
// e.g. https://opensource.apple.com/source/xnu/xnu-6153.81.5/EXTERNAL_HEADERS/mach-o/loader.h
func machoDyldInfo(ctxt *Link) {
ldr := ctxt.loader
rebase := ldr.CreateSymForUpdate(".machorebase", 0)
bind := ldr.CreateSymForUpdate(".machobind", 0)
if !(ctxt.IsPIE() && ctxt.IsInternal()) {
return
}
segId := func(seg *sym.Segment) uint8 {
switch seg {
case &Segtext:
return 1
case &Segrelrodata:
return 2
case &Segdata:
if Segrelrodata.Length > 0 {
return 3
}
return 2
}
panic("unknown segment")
}
dylibId := func(s loader.Sym) int {
slib := ldr.SymDynimplib(s)
for i, lib := range dylib {
if lib == slib {
return i + 1
}
}
return BIND_SPECIAL_DYLIB_FLAT_LOOKUP // don't know where it is from
}
// Rebase table.
// TODO: use more compact encoding. The encoding is stateful, and
// we can use delta encoding.
rebase.AddUint8(REBASE_OPCODE_SET_TYPE_IMM | REBASE_TYPE_POINTER)
for _, r := range machorebase {
seg := ldr.SymSect(r.sym).Seg
off := uint64(ldr.SymValue(r.sym)+r.off) - seg.Vaddr
rebase.AddUint8(REBASE_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB | segId(seg))
rebase.AddUleb(off)
rebase.AddUint8(REBASE_OPCODE_DO_REBASE_IMM_TIMES | 1)
}
rebase.AddUint8(REBASE_OPCODE_DONE)
sz := Rnd(rebase.Size(), 8)
rebase.Grow(sz)
rebase.SetSize(sz)
// Bind table.
// TODO: compact encoding, as above.
// TODO: lazy binding?
got := ctxt.GOT
seg := ldr.SymSect(got).Seg
gotAddr := ldr.SymValue(got)
bind.AddUint8(BIND_OPCODE_SET_TYPE_IMM | BIND_TYPE_POINTER)
for _, r := range machobind {
off := uint64(gotAddr+r.off) - seg.Vaddr
bind.AddUint8(BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB | segId(seg))
bind.AddUleb(off)
d := dylibId(r.targ)
if d > 0 && d < 128 {
bind.AddUint8(BIND_OPCODE_SET_DYLIB_ORDINAL_IMM | uint8(d)&0xf)
} else if d >= 128 {
bind.AddUint8(BIND_OPCODE_SET_DYLIB_ORDINAL_ULEB)
bind.AddUleb(uint64(d))
} else { // d <= 0
bind.AddUint8(BIND_OPCODE_SET_DYLIB_SPECIAL_IMM | uint8(d)&0xf)
}
bind.AddUint8(BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM)
// target symbol name as a C string, with _ prefix
bind.AddUint8('_')
bind.Addstring(ldr.SymExtname(r.targ))
bind.AddUint8(BIND_OPCODE_DO_BIND)
}
bind.AddUint8(BIND_OPCODE_DONE)
sz = Rnd(bind.Size(), 16) // make it 16-byte aligned, see the comment in doMachoLink
bind.Grow(sz)
bind.SetSize(sz)
// TODO: export table.
// The symbols names are encoded as a trie. I'm really too lazy to do that
// for now.
// Without it, the symbols are not dynamically exported, so they cannot be
// e.g. dlsym'd. But internal linking is not the default in that case, so
// it is fine.
}

View file

@ -420,3 +420,21 @@ func (sb *SymbolBuilder) MakeWritable() {
sb.l.SetAttrReadOnly(sb.symIdx, false)
}
}
func (sb *SymbolBuilder) AddUleb(v uint64) {
if v < 128 { // common case: 1 byte
sb.AddUint8(uint8(v))
return
}
for {
c := uint8(v & 0x7f)
v >>= 7
if v != 0 {
c |= 0x80
}
sb.AddUint8(c)
if c&0x80 == 0 {
break
}
}
}