Skip to content
This repository was archived by the owner on Mar 8, 2024. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions .golangci.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
linters:
disable-all: true
enable:
- errcheck
- gosimple
- govet
- ineffassign
- staticcheck
- unused
- gosec
- gofmt
104 changes: 104 additions & 0 deletions analysis.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
package main

import (
"bytes"
"debug/elf"
"encoding/json"
"fmt"
"net/http"
"os"
)

const (
codeCategory SectionCategory = "code"
dataCategory SectionCategory = "data"
bssCategory SectionCategory = "bss"
unknownCategory SectionCategory = "unknown"
)

const (
ghActionRefName = "GITHUB_REF_NAME"
ghActionSha = "GITHUB_SHA"
)

type Size struct {
Text uint64
Data uint64
Bss uint64
}

type Payload struct {
Ref string
Sha string
Size Size
}

type SectionCategory string

func newPayload() (*Payload, error) {
ref := os.Getenv(ghActionRefName)
if len(ref) == 0 {
return nil, fmt.Errorf(fmt.Sprintf("%s not found", ghActionRefName))
}

sha := os.Getenv(ghActionSha)
if len(sha) != 40 {
return nil, fmt.Errorf("malformed sha, not 40 characters long")
}

return &Payload{
Ref: ref,
Sha: sha,
}, nil
}

func pushPayload(token string, url string, payload *Payload) (*http.Response, error) {
body, err := json.Marshal(payload)
if err != nil {
return nil, err
}

req, _ := http.NewRequest("POST", url, bytes.NewReader(body))
req.Header.Add("Authorization", "Bearer "+token)
req.Header.Add("Content-Type", "application/json")

return http.DefaultClient.Do(req)
}

func newSize(file *elf.File) Size {
result := Size{}
for _, section := range file.Sections {
if section.Type == elf.SHT_NULL {
continue
}

switch category(section) {
case codeCategory:
result.Text += section.Size
case dataCategory:
result.Data += section.Size
case bssCategory:
result.Bss += section.Size
}
}

return result
}

func category(section *elf.Section) SectionCategory {
if (section.Flags & elf.SHF_ALLOC) == 0 {
return unknownCategory
}

if section.Type == elf.SHT_PROGBITS {
if (section.Flags & elf.SHF_WRITE) == 0 {
return codeCategory
} else {
return dataCategory
}
} else if section.Type == elf.SHT_NOBITS {
return bssCategory
}

return unknownCategory
}
34 changes: 34 additions & 0 deletions analysis_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package main

import (
"debug/elf"
"testing"
)

var rgctlMeasurements = Size{
Text: 940,
Data: 2,
Bss: 6,
}

func compareSizes(t *testing.T, expect, got Size) {
if expect.Text != got.Text {
t.Errorf("expected text size %d got %d", expect.Text, got.Text)
}
if expect.Data != got.Data {
t.Errorf("expected data size %d got %d", expect.Data, got.Data)
}
if expect.Bss != got.Bss {
t.Errorf("expected bss size %d got %d", expect.Bss, got.Bss)
}
}

func TestNewMeasurements(t *testing.T) {
file, _ := elf.Open("testdata/rgctl.elf")
result := newSize(file)
compareSizes(
t,
rgctlMeasurements,
result,
)
}
91 changes: 47 additions & 44 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,71 +3,74 @@ package main
import (
"debug/elf"
"flag"
"log"
"log/slog"
"net/url"
"os"
)

const (
CODE SectionCategory = "code"
DATA SectionCategory = "data"
BSS SectionCategory = "bss"
UNKNOWN SectionCategory = "unknown"
envToken = "FELF_TOKEN"
envUrl = "FELF_URL"
)

type Measurements struct {
textSize uint64
dataSize uint64
bssSize uint64
}

type SectionCategory string

func main() { cli() }
func main() { os.Exit(cli()) }

func cli() Measurements {
func cli() int {
onlyMeasure := flag.Bool("only-measure", false, "Stop after performing measurements.")
dryRun := flag.Bool("dry-run", false, "Don't push data to the server. Log the payload to stdout.")
flag.Parse()

args := flag.Args()
if len(args) != 1 {
log.Fatal("Only a single positional argument is supported")
slog.Error("only a single positional argument is supported", "args", len(args))
return 2
}

file, err := elf.Open(args[0])
if err != nil {
log.Fatalf(err.Error())
slog.Error(err.Error())
return 74
}

result := Measurements{}
for _, section := range file.Sections {
if section.Type == elf.SHT_NULL {
continue
}
measurements := newSize(file)
slog.Info("analysis done", "size", measurements)

switch sectionCategory(section) {
case CODE:
result.textSize += section.Size
case DATA:
result.dataSize += section.Size
case BSS:
result.bssSize += section.Size
}
if *onlyMeasure {
return 0
}

return result
}
payload, err := newPayload()
if err != nil {
slog.Error(err.Error())
return 3
}
payload.Size = measurements
if *dryRun {
slog.Info("dry mode selected", "payload", *payload)
return 0
}

func sectionCategory(section *elf.Section) SectionCategory {
if (section.Flags & elf.SHF_ALLOC) == 0 {
return UNKNOWN
token := os.Getenv(envToken)
apiUrl := os.Getenv(envUrl)
if len(token) == 0 {
slog.Error("API token missing")
return 4
}
if _, err := url.Parse(apiUrl); err != nil {
slog.Error(err.Error())
return 5
}

if section.Type == elf.SHT_PROGBITS {
if (section.Flags & elf.SHF_WRITE) == 0 {
return CODE
} else {
return DATA
}
} else if section.Type == elf.SHT_NOBITS {
return BSS
response, err := pushPayload(token, apiUrl, payload)
if err != nil {
slog.Error(err.Error())
return 1
}
if response.StatusCode != 200 {
slog.Error("unexpected status code", "code", response.StatusCode)
return 1
}

return UNKNOWN
slog.Info("successful push", "url", apiUrl)
return 0
}
Loading