Skip to content

Commit

Permalink
picobin validation, begin adding uf2, modularize elfutils
Browse files Browse the repository at this point in the history
  • Loading branch information
soypat committed Sep 17, 2024
1 parent 671fd32 commit 0b953f4
Show file tree
Hide file tree
Showing 8 changed files with 256 additions and 67 deletions.
66 changes: 64 additions & 2 deletions blocks.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"encoding/binary"
"errors"
"fmt"
"math"
)

//go:generate stringer -type="ImageType,ExeCPU,ExeChip,ExeSec,ItemType" -linecomment -output stringers.go .
Expand All @@ -13,6 +14,8 @@ import (
const (
BlockMarkerStart = 0xffffded3
BlockMarkerEnd = 0xab123579
minBlockSize = 4 + 4 + 4 + 4 // Header + last_item + Link + Footer
maskByteSize2 = 1
)

var (
Expand All @@ -28,13 +31,38 @@ type Block struct {
Link int
}

// Validate performs all possible static checks on the block and its items for saneness.
func (b Block) Validate() error {
sz := b.Size()
if b.Link > math.MaxInt32 || b.Link < math.MinInt32 {
return errors.New("block link overflows int32")
} else if b.Link < sz && b.Link > -minBlockSize {
return errors.New("block link points to memory inside itself or to impossible block")
}
expectSz := minBlockSize
for i := range b.Items {
if b.Items[i].ItemType() == ItemTypeLast {
return errors.New("block contains last item type")
}
err := b.Items[i].Validate()
if err != nil {
return fmt.Errorf("block item %d (%s): %w", i, b.Items[i].String(), err)
}
expectSz += len(b.Items[i].Data) + 4
}
if sz != expectSz {
return fmt.Errorf("expected %d size, got %d", expectSz, sz)
}
return nil
}

func (b Block) String() string {
return fmt.Sprintf("%s link=%d", b.Items, b.Link)
}

// Size returns the size of the block in bytes, from start of header to end of footer.
func (b Block) Size() int {
size := 4 + 4 + 4 + 4 // Header+ItemLast+Link+Footer.
size := minBlockSize // Header+ItemLast+Link+Footer.
for i := range b.Items {
if b.Items[i].ItemType() == ItemTypeLast {
return -1 // Items should not contain ItemTypeLast
Expand All @@ -44,7 +72,10 @@ func (b Block) Size() int {
return size
}

// NextBlockIdx returns the start and end indices of the next block.
// NextBlockIdx returns the start and end indices of the next block by looking for start marker. If only looking for block start
// one can use the following one-liner:
//
// nextBlockStart := bytes.Index(text, binary.LittleEndian.AppendUint32(nil, picobin.BlockMarkerStart))
func NextBlockIdx(text []byte) (int, int, error) {
start := bytes.Index(text, startMarker)
if start < 0 {
Expand All @@ -62,6 +93,9 @@ func NextBlockIdx(text []byte) (int, int, error) {
return start, end, nil
}

// DecodeBlock decodes the block at the start of text. It returns amount of bytes read until the end block marker end.
// If the block is malformed it fails to decode and returns error. Note block is not fully validated after being decoded.
// Call [Block.Validate] to ensure block saneness after decoding.
func DecodeBlock(text []byte) (Block, int, error) {
if len(text) < 12 {
return Block{}, 0, errors.New("buffer shorter than minimum block size")
Expand Down Expand Up @@ -132,6 +166,34 @@ func DecodeNextItem(blockText []byte) (Item, int, error) {
return item, size, nil
}

// AppendTo appends block to dst without checking data. Call [Block.Validate] before
// AppendTo to ensure data being appended is sane.
func (b Block) AppendTo(dst []byte) []byte {
dst = binary.LittleEndian.AppendUint32(dst, BlockMarkerStart)
size := 0
for i := range b.Items {
size += b.Items[i].Size()
header := b.Items[i].HeaderBytes()
dst = append(dst, header[:]...)
dst = append(dst, b.Items[i].Data...)
}
dst = appendLastItem(dst, size)
dst = binary.LittleEndian.AppendUint32(dst, uint32(int32(b.Link)))
dst = binary.LittleEndian.AppendUint32(dst, BlockMarkerEnd)
return dst
}

func appendLastItem(dst []byte, totalSizeItemsBytes int) []byte {
item := Item{
Head: byte(ItemTypeLast<<1) | maskByteSize2,
SizeAndSpecial: uint16(totalSizeItemsBytes / 4),
TypeData: 0, // pad.
}
header := item.HeaderBytes()
dst = append(dst, header[:]...)
return dst
}

// func makeLOADMAP(absolute bool, entries []loadMapEntry) {
// sizeWords := 1 + len(entries)
// head := item{head: 0x06, s0mod: uint8(sizeWords % 256), s0div: uint8(sizeWords / 256), tpdata: b2u8(absolute) | uint8(len(entries))<<1}
Expand Down
88 changes: 31 additions & 57 deletions cmd/picobin/picobin.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import (
"os"

"github.com/github.com/soypat/picobin"
"golang.org/x/exp/constraints"
"github.com/github.com/soypat/picobin/elfutil"
)

const (
Expand Down Expand Up @@ -52,8 +52,10 @@ func run() error {
switch command {
case "info":
cmd = info

case "dump":
cmd = dump

default:
flag.Usage()
return errors.New("uknown command: " + command)
Expand Down Expand Up @@ -85,12 +87,19 @@ func info(f *elf.File, flags Flags) error {
addr += int64(block.Link)
continue
}

fmt.Printf("BLOCK%d @ Addr=%#x Size=%d Items=%d\n", i, addr, block.Size(), len(block.Items))
for _, item := range block.Items {
fmt.Printf("\t%s\n", item.String())
}
addr += int64(block.Link)
}
for i, block := range blocks {
err = block.Validate()
if err != nil {
fmt.Printf("BLOCK%d failed to validate: %s\n", i, err.Error())
}
}
return nil
}

Expand All @@ -111,7 +120,7 @@ func dump(f *elf.File, flags Flags) error {
break // Last block.
}
data := make([]byte, dataSize)
n, err := readAddr(f, addr+int64(blockSize), data)
n, err := elfutil.ReadAtAddr(f, addr+int64(blockSize), data)
if err != nil {
return err
} else if n == 0 {
Expand All @@ -124,77 +133,42 @@ func dump(f *elf.File, flags Flags) error {
}

func getBlocks(f *elf.File) ([]picobin.Block, int64, error) {
var flash [2 * MB]byte
const flashAddr = 0x10000000
flashEnd, err := readAddr(f, flashAddr, flash[:])
seenAddrs := make(map[int]struct{})
uromStart, _, err := elfutil.GetROMAddr(f)
romStart := int64(uromStart)
if err != nil {
return nil, 0, err
}
start0, _, err := picobin.NextBlockIdx(flash[:flashEnd])
ROM := make([]byte, 2*MB)
flashEnd, err := elfutil.ReadAtAddr(f, romStart, ROM[:])
if err != nil {
return nil, 0, err
}
start0, _, err := picobin.NextBlockIdx(ROM[:flashEnd])
if err != nil {
return nil, 0, err
}
seenAddrs[start0] = struct{}{}
var blocks []picobin.Block
start := start0
startAbs := int64(start) + flashAddr
startAbs := int64(start) + romStart
for {
absAddr := start + flashAddr
block, _, err := picobin.DecodeBlock(flash[start:flashEnd])
absAddr := int64(start) + romStart
block, _, err := picobin.DecodeBlock(ROM[start:flashEnd])
if err != nil {
return blocks, startAbs, fmt.Errorf("decoding block at Addr=%#x: %w", absAddr, err)
}
blocks = append(blocks, block)
nextStart := start + block.Link
if nextStart == start0 {
break // Found last block.
_, alreadySeen := seenAddrs[nextStart]
if alreadySeen {
if nextStart == start0 {
break // Found last block.
}
return blocks, startAbs, fmt.Errorf("odd cyclic block at Addr=%#x", absAddr)
}
seenAddrs[nextStart] = struct{}{}
start = nextStart
}
return blocks, startAbs, nil
}

func readAddr(f *elf.File, addr int64, b []byte) (int, error) {
clear(b)
end := addr + int64(len(b))
maxReadIdx := 0
for _, section := range f.Sections {
saddr := int64(section.Addr)
send := saddr + int64(section.Size)
if aliases(addr, end, saddr, send) {
data, err := section.Data()
if err != nil {
return 0, err
}
sectOff := max(0, int(addr-saddr))
bOff := max(0, int(saddr-addr))
n := copy(b[bOff:], data[sectOff:])
maxReadIdx = max(maxReadIdx, n+bOff)
}
}
return maxReadIdx, nil
}

func aliases(start0, end0, start1, end1 int64) bool {
return start0 < end1 && end0 > start1
}

func clear[E any](a []E) {
var z E
for i := range a {
a[i] = z
}
}

func max[T constraints.Integer](a, b T) T {
if a > b {
return a
}
return b
}

func min[T constraints.Integer](a, b T) T {
if a < b {
return a
}
return b
}
77 changes: 77 additions & 0 deletions elfutil/elfutil.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package elfutil

import (
"debug/elf"
"errors"
"io"
)

func GetROMAddr(f *elf.File) (start, end uint64, err error) {
// Find the lowest section address.
startAddr := ^uint64(0)
endAddr := uint64(0)
for _, section := range f.Sections {
if section.Type != elf.SHT_PROGBITS || section.Flags&elf.SHF_ALLOC == 0 {
continue
}
if section.Addr < startAddr {
startAddr = section.Addr
}
if section.Addr+section.Size > endAddr {
endAddr = section.Addr + section.Size
}
}
if startAddr > endAddr {
return 0, 0, errors.New("no ROM sections found")
}
return startAddr, end, nil
}

// ReadAtAddr reads from the binary sections representing read-only-memory (ROM) starting at address addr.
func ReadAtAddr(f *elf.File, addr int64, b []byte) (int, error) {
clear(b)
end := addr + int64(len(b))
maxReadIdx := 0
for _, prog := range f.Progs {
if prog.Type != elf.PT_LOAD || prog.Filesz == 0 || prog.Off == 0 {
continue
}
paddr := int64(prog.Paddr)
pend := paddr + int64(prog.Memsz)
if aliases(addr, end, paddr, pend) {
progOff := max(0, int(addr-paddr))
bOff := max(0, int(paddr-addr))
n, err := prog.ReadAt(b[bOff:], int64(progOff))
if err != nil && err != io.EOF {
return maxReadIdx, err
}
maxReadIdx = max(maxReadIdx, n+bOff)
}
}
return maxReadIdx, nil
}

func aliases(start0, end0, start1, end1 int64) bool {
return start0 < end1 && end0 > start1
}

func clear[E any](a []E) {
var z E
for i := range a {
a[i] = z
}
}

func max[T ~int | ~int64](a, b T) T {
if a > b {
return a
}
return b
}

func min[T ~int | ~int64](a, b T) T {
if a < b {
return a
}
return b
}
6 changes: 1 addition & 5 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,7 +1,3 @@
module github.com/github.com/soypat/picobin

go 1.22.0

toolchain go1.23.0

require golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 // indirect
go 1.19.0
2 changes: 0 additions & 2 deletions go.sum

This file was deleted.

10 changes: 9 additions & 1 deletion items.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,14 @@ func (it Item) HeaderBytes() [4]byte {
return [4]byte{it.Head, byte(it.SizeAndSpecial), byte(it.SizeAndSpecial >> 8), it.TypeData}
}

func (it Item) Validate() error {
sz := it.Size()
if sz != 4+len(it.Data) {
return fmt.Errorf("item size %d, but have %d data", sz, len(it.Data))
}
return nil
}

type ItemType uint8

// ItemType definitions.
Expand All @@ -42,7 +50,7 @@ const (
ItemTypeLast ItemType = 0x7f // last
)

func (it Item) sizeflag() bool { return it.Head&1 != 0 }
func (it Item) sizeflag() bool { return it.Head&maskByteSize2 != 0 }

// Size returns the size in bytes of the item.
func (it Item) Size() int {
Expand Down
8 changes: 8 additions & 0 deletions picobin_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,14 @@ func TestBlinkyParse(t *testing.T) {
if start2 != start0 {
t.Errorf("expected 2 blocks only, second linking to first, got start2=%d", start2)
}
err = blk0.Validate()
if err != nil {
t.Error(err)
}
err = blk1.Validate()
if err != nil {
t.Error(err)
}
t.Logf("BLOCK0:%s BLOCK1:%s", blk0, blk1)
}

Expand Down
Loading

0 comments on commit 0b953f4

Please sign in to comment.