Add relocation CRUD and dyld bind kinds

add remove APIs for ELF/Mach-O/PE relocations and relocation sections
allow ELF alloc relocation sections to grow via new PT_LOAD; stabilize section write order
add Mach-O scattered relocs plus weak/lazy bind stream support
add PE COFF relocation helpers (symbol lookup)
update README and tests
This commit is contained in:
awgh
2026-01-28 15:00:15 -08:00
parent dd7757eeb3
commit 4e22595eaa
10 changed files with 607 additions and 32 deletions
+3 -3
View File
@@ -2,9 +2,9 @@
We have forked the debug/ folder from the standard library, to take direct control of the debug/elf, debug/macho, and debug/pe binary format parsers. To these parsers, we have added the ability to also generate executable files from the parsed intermediate data structures. This lets us load a file with debug parsers, make changes by interacting with the parser structures, and then write those changes back out to a new file.
## Relocation editing
- `debug/elf`: `AddRelocation`, `AddRelocations`, `ReplaceRelocations`, `AddRelocationForSymbol`, `AddRelocationForAddr`, `AddRelocationsToRelocSection`
- `debug/macho`: `AddRelocation`, `AddRelocations`, `ReplaceRelocations`, `AddRelocationForSymbol`, `AddRelocationForSymbolWithDylibOrdinal`, `SetDylibOrdinalForSymbol`
- `debug/pe`: `AddBaseRelocation`, `ReplaceBaseRelocations`, `AddBaseReloc`, `AddSectionRelocation`, `ReplaceSectionRelocations`
- `debug/elf`: `AddRelocation`, `AddRelocations`, `ReplaceRelocations`, `RemoveRelocations`, `AddRelocationForSymbol`, `AddRelocationForAddr`, `AddRelocationsToRelocSection`, `RemoveRelocationsFromRelocSection` (alloc relocs can now grow via a new PT_LOAD when needed)
- `debug/macho`: `AddRelocation`, `AddRelocations`, `ReplaceRelocations`, `RemoveRelocations`, `AddScatteredRelocation`, `AddRelocationForSymbol`, `AddRelocationForSymbolWithDylibOrdinal`, `SetDylibOrdinalForSymbol`, `SetBindKindForSymbol`
- `debug/pe`: `AddBaseRelocation`, `ReplaceBaseRelocations`, `RemoveBaseRelocations`, `AddBaseReloc`, `AddSectionRelocation`, `AddSectionRelocationForSymbol`, `ReplaceSectionRelocations`, `RemoveSectionRelocations`
## Read more about the project here:
+100 -1
View File
@@ -23,6 +23,30 @@ func (f *File) ReplaceRelocations(target *Section, rels interface{}) error {
return f.addRelocations(target, rels, true, nil)
}
// RemoveRelocations clears relocation entries for the target section.
func (f *File) RemoveRelocations(target *Section) error {
if target == nil {
return errors.New("target section is nil")
}
targetIndex, ok := f.sectionIndex(target)
if !ok {
return errors.New("target section not found in file")
}
relocSecRel, _ := f.relocationSection(targetIndex, SHT_REL)
relocSecRela, _ := f.relocationSection(targetIndex, SHT_RELA)
if relocSecRel == nil && relocSecRela == nil {
return nil
}
if relocSecRel != nil {
relocSecRel.Replace(bytes.NewReader(nil), 0)
}
if relocSecRela != nil {
relocSecRela.Replace(bytes.NewReader(nil), 0)
}
f.updateDynamicRelocTags()
return nil
}
// AddRelocationsToRelocSection appends relocation entries to a specific relocation section
// (e.g. ".rela.dyn" or ".rel.plt").
func (f *File) AddRelocationsToRelocSection(sectionName string, rels interface{}) error {
@@ -67,6 +91,20 @@ func (f *File) AddRelocationsToRelocSection(sectionName string, rels interface{}
return nil
}
// RemoveRelocationsFromRelocSection clears relocation entries for the named relocation section.
func (f *File) RemoveRelocationsFromRelocSection(sectionName string) error {
relocSec := f.Section(sectionName)
if relocSec == nil {
return fmt.Errorf("relocation section %q not found", sectionName)
}
if relocSec.Type != SHT_REL && relocSec.Type != SHT_RELA {
return fmt.Errorf("section %q is not a relocation section", sectionName)
}
relocSec.Replace(bytes.NewReader(nil), 0)
f.updateDynamicRelocTags()
return nil
}
// AddRelocationForSymbol builds and adds a relocation for the named symbol.
// If addend is nil, a REL entry is created. Otherwise a RELA entry is used.
func (f *File) AddRelocationForSymbol(sectionName, symbolName string, offset uint64, rType uint32, addend *int64) error {
@@ -476,7 +514,7 @@ func (f *File) relayoutAllocRelocationSection(section *Section) error {
newOff := alignUp(prog.Off+prog.Filesz, align)
newEnd := newOff + section.FileSize
if nextLoadOff != 0 && newEnd > nextLoadOff {
return fmt.Errorf("no room to grow %q within PT_LOAD", section.Name)
return f.relayoutAllocRelocationSectionNewLoad(section)
}
newAddr := prog.Vaddr + (newOff - prog.Off)
section.Offset = newOff
@@ -496,6 +534,67 @@ func (f *File) relayoutAllocRelocationSection(section *Section) error {
return nil
}
func (f *File) relayoutAllocRelocationSectionNewLoad(section *Section) error {
align := section.Addralign
if align == 0 {
align = relocationAlign(f.Class)
}
var lastLoad *Prog
for _, p := range f.Progs {
if p.Type != PT_LOAD {
continue
}
if lastLoad == nil || p.Off > lastLoad.Off {
lastLoad = p
}
}
if lastLoad == nil {
return fmt.Errorf("no PT_LOAD segments available")
}
baseOff := lastLoad.Off + lastLoad.Filesz
fileEnd := f.maxFileEnd()
if fileEnd > baseOff {
baseOff = fileEnd
}
newOff := alignUp(baseOff, align)
newAddr := lastLoad.Vaddr + (newOff - lastLoad.Off)
section.Offset = newOff
section.Addr = newAddr
end := newOff + section.FileSize
if end > lastLoad.Off+lastLoad.Filesz {
delta := end - (lastLoad.Off + lastLoad.Filesz)
lastLoad.Filesz += delta
lastLoad.Memsz += delta
}
if end := section.Offset + section.FileSize; int64(end) > f.SHTOffset {
shtAlign := uint64(4)
if f.Class == ELFCLASS64 {
shtAlign = 8
}
f.SHTOffset = int64(alignUp(end, shtAlign))
}
return nil
}
func (f *File) maxFileEnd() uint64 {
var maxEnd uint64
for _, s := range f.Sections {
if s.Type == SHT_NOBITS || s.FileSize == 0 {
continue
}
end := s.Offset + s.FileSize
if end > maxEnd {
maxEnd = end
}
}
if f.SHTOffset > int64(maxEnd) {
maxEnd = uint64(f.SHTOffset)
}
return maxEnd
}
func (f *File) updateDynamicRelocTags() {
if len(f.DynTags) == 0 {
return
+94 -5
View File
@@ -3,7 +3,6 @@ package elf
import (
"bytes"
"path"
"strings"
"testing"
)
@@ -63,15 +62,13 @@ func TestAddDynamicRelocationUpdatesTags(t *testing.T) {
if relDyn == nil {
t.Fatalf("rela.dyn missing in test binary")
}
origSize := relDyn.Size
rel := Rela64{
Off: 0,
Info: R_INFO(0, uint32(R_X86_64_RELATIVE)),
Addend: 0,
}
if err := f.AddRelocationsToRelocSection(".rela.dyn", []Rela64{rel}); err != nil {
if strings.Contains(err.Error(), "no room to grow") {
t.Skip("test binary has no space to grow .rela.dyn")
}
t.Fatalf("add to rela.dyn: %v", err)
}
@@ -85,7 +82,7 @@ func TestAddDynamicRelocationUpdatesTags(t *testing.T) {
}
relDyn2 := f2.Section(".rela.dyn")
if relDyn2 == nil || relDyn2.Size <= relDyn.Size {
if relDyn2 == nil || relDyn2.Size <= origSize {
t.Fatalf("rela.dyn not grown")
}
@@ -130,3 +127,95 @@ func TestAddRelocationCreatesRelText(t *testing.T) {
t.Fatalf("rel.text size invalid: size=%d entsize=%d", rel.Size, rel.Entsize)
}
}
func TestPltRelocationUpdatesTags(t *testing.T) {
f, err := Open(path.Join("testdata", "gcc-amd64-linux-exec"))
if err != nil {
t.Fatalf("open elf: %v", err)
}
defer f.Close()
relPlt := f.Section(".rela.plt")
if relPlt == nil {
t.Skip("no .rela.plt section in test binary")
}
origSize := relPlt.Size
rel := Rela64{
Off: 0,
Info: R_INFO(0, uint32(R_X86_64_JMP_SLOT)),
Addend: 0,
}
if err := f.AddRelocationsToRelocSection(".rela.plt", []Rela64{rel}); err != nil {
t.Fatalf("add to rela.plt: %v", err)
}
out, err := f.Bytes()
if err != nil {
t.Fatalf("write: %v", err)
}
f2, err := NewFile(bytes.NewReader(out))
if err != nil {
t.Fatalf("reopen: %v", err)
}
relPlt2 := f2.Section(".rela.plt")
if relPlt2 == nil || relPlt2.Size <= origSize {
t.Fatalf("rela.plt not grown")
}
dynTags := map[DynTag]uint64{}
for _, tag := range f2.DynTags {
dynTags[tag.Tag] = tag.Value
}
if dynTags[DT_JMPREL] != relPlt2.Addr {
t.Fatalf("DT_JMPREL not updated")
}
if dynTags[DT_PLTRELSZ] != relPlt2.Size {
t.Fatalf("DT_PLTRELSZ not updated")
}
if dynTags[DT_PLTREL] != uint64(DT_RELA) {
t.Fatalf("DT_PLTREL not updated")
}
}
func TestRemoveRelocationsFromSection(t *testing.T) {
f, err := Open(path.Join("testdata", "gcc-amd64-linux-exec"))
if err != nil {
t.Fatalf("open elf: %v", err)
}
defer f.Close()
addend := int64(0)
syms, err := f.Symbols()
if err != nil {
t.Fatalf("symbols: %v", err)
}
var symName string
for _, sym := range syms {
if sym.Name != "" && sym.Section != SHN_UNDEF {
symName = sym.Name
break
}
}
if symName == "" {
t.Skip("no suitable symbol found")
}
if err := f.AddRelocationForSymbol(".text", symName, 0, uint32(R_X86_64_64), &addend); err != nil {
t.Fatalf("add relocation: %v", err)
}
if err := f.RemoveRelocations(f.Section(".text")); err != nil {
t.Fatalf("remove relocations: %v", err)
}
out, err := f.Bytes()
if err != nil {
t.Fatalf("write: %v", err)
}
f2, err := NewFile(bytes.NewReader(out))
if err != nil {
t.Fatalf("reopen: %v", err)
}
if sec := f2.Section(".rela.text"); sec != nil && sec.Size != 0 {
t.Fatalf("expected .rela.text cleared")
}
if sec := f2.Section(".rel.text"); sec != nil && sec.Size != 0 {
t.Fatalf("expected .rel.text cleared")
}
}
+5 -2
View File
@@ -7,6 +7,7 @@ import (
"io/ioutil"
"log"
"os"
"sort"
)
// Bytes - returns the bytes of an Elf file
@@ -152,8 +153,10 @@ func (elfFile *File) Bytes() ([]byte, error) {
}
}
sortedSections := elfFile.Sections[:]
//sort.Slice(sortedSections, func(a, b int) bool { return elfFile.Sections[a].Link < elfFile.Sections[b].Link })
sortedSections := append([]*Section(nil), elfFile.Sections...)
sort.SliceStable(sortedSections, func(i, j int) bool {
return sortedSections[i].Offset < sortedSections[j].Offset
})
for _, s := range sortedSections {
//log.Printf("Writing section: %s type: %+v\n", s.Name, s.Type)
+1
View File
@@ -36,6 +36,7 @@ type File struct {
Insertion []byte
dylibOrdinalBySymbol map[uint32]uint8
bindKindBySymbol map[uint32]BindKind
closer io.Closer
}
+60
View File
@@ -5,6 +5,15 @@ import (
"fmt"
)
// BindKind controls which dyld binding stream is used for an extern relocation.
type BindKind uint8
const (
BindNormal BindKind = iota
BindWeak
BindLazy
)
// AddRelocation appends a relocation to the named section.
func (f *File) AddRelocation(sectionName string, rel Reloc) error {
section := f.Section(sectionName)
@@ -15,6 +24,25 @@ func (f *File) AddRelocation(sectionName string, rel Reloc) error {
return nil
}
// AddScatteredRelocation appends a scattered relocation to the named section.
func (f *File) AddScatteredRelocation(sectionName string, addr uint32, value uint32, relType uint8, length uint8, pcrel bool) error {
section := f.Section(sectionName)
if section == nil {
return fmt.Errorf("section %q not found", sectionName)
}
rel := Reloc{
Addr: addr,
Value: value,
Type: relType,
Len: length,
Pcrel: pcrel,
Extern: false,
Scattered: true,
}
section.Relocs = append(section.Relocs, rel)
return nil
}
// AddRelocations appends relocations to the named section.
func (f *File) AddRelocations(sectionName string, rels []Reloc) error {
section := f.Section(sectionName)
@@ -35,6 +63,16 @@ func (f *File) ReplaceRelocations(sectionName string, rels []Reloc) error {
return nil
}
// RemoveRelocations clears relocations for the named section.
func (f *File) RemoveRelocations(sectionName string) error {
section := f.Section(sectionName)
if section == nil {
return fmt.Errorf("section %q not found", sectionName)
}
section.Relocs = section.Relocs[:0]
return nil
}
// AddRelocationForSymbol creates a non-scattered relocation that references a symbol.
func (f *File) AddRelocationForSymbol(sectionName, symbolName string, addr uint32, relType uint8, length uint8, pcrel bool) error {
if f.Symtab == nil {
@@ -108,3 +146,25 @@ func (f *File) SetDylibOrdinalForSymbolIndex(index uint32, ordinal uint8) error
f.dylibOrdinalBySymbol[index] = ordinal
return nil
}
// SetBindKindForSymbol records a bind kind for the named symbol.
func (f *File) SetBindKindForSymbol(symbolName string, kind BindKind) error {
if f.Symtab == nil {
return errors.New("symbol table not available")
}
for i, sym := range f.Symtab.Syms {
if sym.Name == symbolName {
return f.SetBindKindForSymbolIndex(uint32(i), kind)
}
}
return fmt.Errorf("symbol %q not found", symbolName)
}
// SetBindKindForSymbolIndex records a bind kind for the symbol index.
func (f *File) SetBindKindForSymbolIndex(index uint32, kind BindKind) error {
if f.bindKindBySymbol == nil {
f.bindKindBySymbol = map[uint32]BindKind{}
}
f.bindKindBySymbol[index] = kind
return nil
}
+133
View File
@@ -91,3 +91,136 @@ func TestDyldBindOrdinal(t *testing.T) {
t.Fatalf("bind info missing ordinal opcode")
}
}
func TestScatteredRelocationRoundTrip(t *testing.T) {
f, err := Open(path.Join("testdata", "gcc-amd64-darwin-exec"))
if err != nil {
t.Fatalf("open macho: %v", err)
}
defer f.Close()
sec := f.Section("__text")
if sec == nil {
t.Fatalf("missing __text section")
}
origCount := len(sec.Relocs)
if err := f.AddScatteredRelocation("__text", 0, 0, 0, 2, false); err != nil {
t.Fatalf("add scattered relocation: %v", err)
}
out, err := f.Bytes()
if err != nil {
t.Fatalf("write: %v", err)
}
f2, err := NewFile(bytes.NewReader(out))
if err != nil {
t.Fatalf("reopen: %v", err)
}
sec2 := f2.Section("__text")
if sec2 == nil || len(sec2.Relocs) != origCount+1 {
t.Fatalf("relocations not persisted")
}
found := false
for _, rel := range sec2.Relocs {
if rel.Scattered {
found = true
break
}
}
if !found {
t.Fatalf("scattered relocation not preserved")
}
}
func TestRemoveRelocations(t *testing.T) {
f, err := Open(path.Join("testdata", "gcc-amd64-darwin-exec"))
if err != nil {
t.Fatalf("open macho: %v", err)
}
defer f.Close()
sec := f.Section("__text")
if sec == nil {
t.Fatalf("missing __text section")
}
if err := f.AddRelocation("__text", Reloc{Addr: 0, Type: 0, Len: 3, Pcrel: false, Extern: false}); err != nil {
t.Fatalf("add relocation: %v", err)
}
if err := f.RemoveRelocations("__text"); err != nil {
t.Fatalf("remove relocation: %v", err)
}
out, err := f.Bytes()
if err != nil {
t.Fatalf("write: %v", err)
}
f2, err := NewFile(bytes.NewReader(out))
if err != nil {
t.Fatalf("reopen: %v", err)
}
sec2 := f2.Section("__text")
if sec2 == nil {
t.Fatalf("missing __text section")
}
if sec2.Nreloc != 0 || len(sec2.Relocs) != 0 {
t.Fatalf("relocations not removed")
}
}
func TestWeakAndLazyBindKinds(t *testing.T) {
f, err := Open(path.Join("testdata", "gcc-amd64-darwin-exec"))
if err != nil {
t.Fatalf("open macho: %v", err)
}
defer f.Close()
if f.DylinkInfo == nil || f.Symtab == nil {
t.Skip("missing dyld info or symbol table")
}
sec := f.Section("__text")
if sec == nil {
t.Fatalf("missing __text section")
}
var symName string
for _, sym := range f.Symtab.Syms {
if sym.Name != "" {
symName = sym.Name
break
}
}
if symName == "" {
t.Skip("no suitable symbol found")
}
if err := f.AddRelocationForSymbol("__text", symName, 0, 0, 3, false); err != nil {
t.Fatalf("add relocation: %v", err)
}
if err := f.SetBindKindForSymbol(symName, BindWeak); err != nil {
t.Fatalf("set weak bind: %v", err)
}
if err := f.AddRelocationForSymbol("__text", symName, 4, 0, 3, false); err != nil {
t.Fatalf("add relocation: %v", err)
}
if err := f.SetBindKindForSymbol(symName, BindLazy); err != nil {
t.Fatalf("set lazy bind: %v", err)
}
out, err := f.Bytes()
if err != nil {
t.Fatalf("write: %v", err)
}
f2, err := NewFile(bytes.NewReader(out))
if err != nil {
t.Fatalf("reopen: %v", err)
}
if f2.DylinkInfo == nil {
t.Fatalf("missing dyld info")
}
if len(f2.DylinkInfo.WeakBindingDat) == 0 {
t.Fatalf("weak binding info missing")
}
if len(f2.DylinkInfo.LazyBindingDat) == 0 {
t.Fatalf("lazy binding info missing")
}
}
+64 -20
View File
@@ -73,17 +73,17 @@ func (f *File) prepareDyldInfoFromRelocs() error {
if f.DylinkInfo == nil {
return nil
}
rebaseDat, bindDat, err := f.encodeDyldInfoFromRelocs()
rebaseDat, bindDat, weakBindDat, lazyBindDat, err := f.encodeDyldInfoFromRelocs()
if err != nil {
return err
}
if len(rebaseDat) == 0 && len(bindDat) == 0 {
if len(rebaseDat) == 0 && len(bindDat) == 0 && len(weakBindDat) == 0 && len(lazyBindDat) == 0 {
return nil
}
start := alignUp64(f.endOfSections(), 4)
limit := f.dyldInfoEndLimit()
total := uint64(len(rebaseDat) + len(bindDat))
total := uint64(len(rebaseDat) + len(bindDat) + len(weakBindDat) + len(lazyBindDat))
if limit != 0 && start+total > limit {
return fmt.Errorf("not enough room for dyld info")
}
@@ -97,6 +97,16 @@ func (f *File) prepareDyldInfoFromRelocs() error {
f.DylinkInfo.BindingInfoDat = bindDat
f.DylinkInfo.BindingInfoLen = uint32(len(bindDat))
f.DylinkInfo.BindingInfoOffset = offset
offset += uint64(len(bindDat))
f.DylinkInfo.WeakBindingDat = weakBindDat
f.DylinkInfo.WeakBindingLen = uint32(len(weakBindDat))
f.DylinkInfo.WeakBindingOffset = offset
offset += uint64(len(weakBindDat))
f.DylinkInfo.LazyBindingDat = lazyBindDat
f.DylinkInfo.LazyBindingLen = uint32(len(lazyBindDat))
f.DylinkInfo.LazyBindingOffset = offset
return f.refreshDylinkInfoLoadBytes()
}
@@ -145,19 +155,28 @@ func (f *File) dyldInfoEndLimit() uint64 {
return limit
}
func (f *File) encodeDyldInfoFromRelocs() ([]byte, []byte, error) {
func (f *File) encodeDyldInfoFromRelocs() ([]byte, []byte, []byte, []byte, error) {
segments := f.segmentOrdinals()
if len(segments) == 0 {
return nil, nil, nil
return nil, nil, nil, nil, nil
}
var rebase bytes.Buffer
var bind bytes.Buffer
var weak bytes.Buffer
var lazy bytes.Buffer
rebase.WriteByte(rebaseOpcodeSetTypeImm | rebaseTypePointer)
bind.WriteByte(bindOpcodeSetTypeImm | bindTypePointer)
weak.WriteByte(bindOpcodeSetTypeImm | bindTypePointer)
lazy.WriteByte(bindOpcodeSetTypeImm | bindTypePointer)
currentOrdinal := uint8(0)
weakOrdinal := uint8(0)
lazyOrdinal := uint8(0)
bind.WriteByte(bindOpcodeSetDylibOrdinalImm | currentOrdinal)
weak.WriteByte(bindOpcodeSetDylibOrdinalImm | weakOrdinal)
lazy.WriteByte(bindOpcodeSetDylibOrdinalImm | lazyOrdinal)
for _, s := range f.Sections {
if len(s.Relocs) == 0 {
@@ -165,35 +184,45 @@ func (f *File) encodeDyldInfoFromRelocs() ([]byte, []byte, error) {
}
ordinal, ok := segments[s.Seg]
if !ok {
return nil, nil, fmt.Errorf("unknown segment for section %q", s.Name)
return nil, nil, nil, nil, fmt.Errorf("unknown segment for section %q", s.Name)
}
seg := byte(ordinal & 0x0f)
for _, rel := range s.Relocs {
offset := uint64(s.Addr) + uint64(rel.Addr)
segBase := f.segmentAddr(s.Seg)
if offset < segBase {
return nil, nil, fmt.Errorf("relocation offset underflows segment %q", s.Seg)
return nil, nil, nil, nil, fmt.Errorf("relocation offset underflows segment %q", s.Seg)
}
segOffset := offset - segBase
if rel.Extern {
ordinal, err := f.dylibOrdinalForSymbol(rel.Value)
if err != nil {
return nil, nil, err
return nil, nil, nil, nil, err
}
if ordinal != currentOrdinal {
bind.WriteByte(bindOpcodeSetDylibOrdinalImm | ordinal)
currentOrdinal = ordinal
stream := &bind
streamOrdinal := &currentOrdinal
switch f.bindKindForSymbol(rel.Value) {
case BindWeak:
stream = &weak
streamOrdinal = &weakOrdinal
case BindLazy:
stream = &lazy
streamOrdinal = &lazyOrdinal
}
if ordinal != *streamOrdinal {
stream.WriteByte(bindOpcodeSetDylibOrdinalImm | ordinal)
*streamOrdinal = ordinal
}
name, err := f.symbolName(rel.Value)
if err != nil {
return nil, nil, err
return nil, nil, nil, nil, err
}
bind.WriteByte(bindOpcodeSetSegmentAndOffsetULEB | seg)
bind.Write(encodeULEB128(segOffset))
bind.WriteByte(bindOpcodeSetSymbolTrailingFlags | 0)
bind.WriteString(name)
bind.WriteByte(0)
bind.WriteByte(bindOpcodeDoBind)
stream.WriteByte(bindOpcodeSetSegmentAndOffsetULEB | seg)
stream.Write(encodeULEB128(segOffset))
stream.WriteByte(bindOpcodeSetSymbolTrailingFlags | 0)
stream.WriteString(name)
stream.WriteByte(0)
stream.WriteByte(bindOpcodeDoBind)
} else {
rebase.WriteByte(rebaseOpcodeSetSegmentAndOffsetULEB | seg)
rebase.Write(encodeULEB128(segOffset))
@@ -203,9 +232,17 @@ func (f *File) encodeDyldInfoFromRelocs() ([]byte, []byte, error) {
}
rebase.WriteByte(rebaseOpcodeDone)
bind.WriteByte(bindOpcodeDone)
if bind.Len() > 1 {
bind.WriteByte(bindOpcodeDone)
}
if weak.Len() > 1 {
weak.WriteByte(bindOpcodeDone)
}
if lazy.Len() > 1 {
lazy.WriteByte(bindOpcodeDone)
}
return rebase.Bytes(), bind.Bytes(), nil
return rebase.Bytes(), bind.Bytes(), weak.Bytes(), lazy.Bytes(), nil
}
func (f *File) segmentOrdinals() map[string]int {
@@ -250,6 +287,13 @@ func (f *File) dylibOrdinalForSymbol(index uint32) (uint8, error) {
return ordinal, nil
}
func (f *File) bindKindForSymbol(index uint32) BindKind {
if f.bindKindBySymbol == nil {
return BindNormal
}
return f.bindKindBySymbol[index]
}
func encodeULEB128(value uint64) []byte {
var out []byte
for {
+49 -1
View File
@@ -1,6 +1,8 @@
package pe
import "fmt"
import (
"fmt"
)
// AddBaseRelocation appends a relocation block to the base relocation table.
func (f *File) AddBaseRelocation(block RelocationTableEntry) {
@@ -20,6 +22,15 @@ func (f *File) ReplaceBaseRelocations(blocks []RelocationTableEntry) {
*f.BaseRelocationTable = append((*f.BaseRelocationTable)[:0], blocks...)
}
// RemoveBaseRelocations clears all base relocation entries.
func (f *File) RemoveBaseRelocations() {
if f.BaseRelocationTable == nil {
entries := []RelocationTableEntry{}
f.BaseRelocationTable = &entries
}
*f.BaseRelocationTable = (*f.BaseRelocationTable)[:0]
}
// AddBaseReloc adds a single base relocation to the block for the containing page.
func (f *File) AddBaseReloc(rva uint32, typ byte) {
page := rva &^ 0x0fff
@@ -52,6 +63,33 @@ func (f *File) AddSectionRelocation(sectionName string, rel Reloc) error {
return nil
}
// AddSectionRelocationForSymbol appends a COFF relocation using a symbol name lookup.
func (f *File) AddSectionRelocationForSymbol(sectionName string, symbolName string, rel Reloc) error {
index, err := f.SymbolIndexByName(symbolName)
if err != nil {
return err
}
rel.SymbolTableIndex = index
return f.AddSectionRelocation(sectionName, rel)
}
// SymbolIndexByName returns the COFF symbol table index for the given name.
func (f *File) SymbolIndexByName(symbolName string) (uint32, error) {
if len(f.COFFSymbols) == 0 {
return 0, fmt.Errorf("no COFF symbols available")
}
for i := range f.COFFSymbols {
name, err := f.COFFSymbols[i].FullName(f.StringTable)
if err != nil {
return 0, err
}
if name == symbolName {
return uint32(i), nil
}
}
return 0, fmt.Errorf("symbol %q not found", symbolName)
}
// ReplaceSectionRelocations replaces COFF relocations for a section.
func (f *File) ReplaceSectionRelocations(sectionName string, rels []Reloc) error {
section := f.Section(sectionName)
@@ -61,3 +99,13 @@ func (f *File) ReplaceSectionRelocations(sectionName string, rels []Reloc) error
section.Relocs = append(section.Relocs[:0], rels...)
return nil
}
// RemoveSectionRelocations clears COFF relocations for a section.
func (f *File) RemoveSectionRelocations(sectionName string) error {
section := f.Section(sectionName)
if section == nil {
return fmt.Errorf("section %q not found", sectionName)
}
section.Relocs = section.Relocs[:0]
return nil
}
+98
View File
@@ -58,3 +58,101 @@ func TestAddBaseAndSectionRelocations(t *testing.T) {
t.Fatalf("relocs stripped flag still set")
}
}
func TestAddSectionRelocationForSymbol(t *testing.T) {
f, err := Open(path.Join("testdata", "gcc-386-mingw-exec"))
if err != nil {
t.Fatalf("open pe: %v", err)
}
defer f.Close()
if len(f.COFFSymbols) == 0 {
t.Skip("no COFF symbols available")
}
var symName string
for i := range f.COFFSymbols {
name, err := f.COFFSymbols[i].FullName(f.StringTable)
if err != nil {
t.Fatalf("symbol name: %v", err)
}
if name != "" {
symName = name
break
}
}
if symName == "" {
t.Skip("no suitable symbol found")
}
sec := f.Sections[0]
rel := Reloc{VirtualAddress: 0, Type: 0}
if err := f.AddSectionRelocationForSymbol(sec.Name, symName, rel); err != nil {
t.Fatalf("add relocation: %v", err)
}
out, err := f.Bytes()
if err != nil {
t.Fatalf("write: %v", err)
}
f2, err := NewFile(bytes.NewReader(out))
if err != nil {
t.Fatalf("reopen: %v", err)
}
sec2 := f2.Section(sec.Name)
if sec2 == nil || len(sec2.Relocs) == 0 {
t.Fatalf("relocations not written")
}
idx, err := f.SymbolIndexByName(symName)
if err != nil {
t.Fatalf("symbol index: %v", err)
}
found := false
for _, r := range sec2.Relocs {
if r.SymbolTableIndex == idx {
found = true
break
}
}
if !found {
t.Fatalf("relocation with symbol index not found")
}
}
func TestRemoveRelocations(t *testing.T) {
f, err := Open(path.Join("testdata", "gcc-386-mingw-exec"))
if err != nil {
t.Fatalf("open pe: %v", err)
}
defer f.Close()
if len(f.Sections) == 0 {
t.Fatalf("no sections")
}
sec := f.Sections[0]
f.AddBaseReloc(sec.VirtualAddress, IMAGE_REL_BASED_HIGHLOW)
if err := f.AddSectionRelocation(sec.Name, Reloc{VirtualAddress: 0, SymbolTableIndex: 0, Type: 0}); err != nil {
t.Fatalf("add relocation: %v", err)
}
f.RemoveBaseRelocations()
if err := f.RemoveSectionRelocations(sec.Name); err != nil {
t.Fatalf("remove relocation: %v", err)
}
out, err := f.Bytes()
if err != nil {
t.Fatalf("write: %v", err)
}
f2, err := NewFile(bytes.NewReader(out))
if err != nil {
t.Fatalf("reopen: %v", err)
}
if f2.BaseRelocationTable != nil && len(*f2.BaseRelocationTable) != 0 {
t.Fatalf("base relocations not removed")
}
sec2 := f2.Section(sec.Name)
if sec2 == nil {
t.Fatalf("missing section")
}
if len(sec2.Relocs) != 0 || sec2.NumberOfRelocations != 0 {
t.Fatalf("section relocations not removed")
}
}