Calculate the size of an ELF

How can I calculate the size of an ELF executable in Go?

I am looking for something along the lines of the following C code:

Currently I am using Cgo but I would prefer a pure-Go solution.

2 Likes

Welcome to Go Forum! :rocket::fireworks:


Don’t get what you mean, ls -l should be able to show the size of the binary right? Or do you mean the breakdown from the total size?

There is an elf package from standard library. Will that help? https://golang.org/pkg/debug/elf/

1 Like

Thanks for the warm welcome @hollowaykeanho.

What I didn’t mention is that I need to calculate the size of the ELF binary using the information from the ELF headers, not by looking at the file size on disk, because the file on disk has random data appended to the ELF.

Code examples in other languages:

Since I am really just starting out with Go I don’t quite know yet how to translate this into Go.

1 Like

Well, one can start off by reading through the documentation (https://golang.org/pkg/debug/elf/) and try to understand what is going with it. Then read up the source code (https://golang.org/src/debug/elf/file.go) espcially Line 240 by searching the Header32 and Header64 strcuture keywords pointed by @rot13.

This is one code example for reproduce the shoff, shentsize, and shnum through analyzing the NewFile.

Code
// Authors:
//   1. Holloway, Chew Kean Ho <kean.ho.chew@zoralab.com>

// main is a demostration for elf data reader.
package main

import (
	"debug/elf"
	"encoding/binary"
	"fmt"
	"io"
	"os"
)

func ioReader(file string) (io.ReaderAt, int64) {
	f, err := os.Open(file)
	if err != nil {
		panic(err)
	}

	fi, err := f.Stat()
	if err != nil {
		panic(err)
	}

	return f, fi.Size()
}

func main() {
	if len(os.Args) < 2 {
		fmt.Println(`Usage: ./elftester /path/to/elf/file`)
		os.Exit(1)
	}

	// Open given elf file
	f, size := ioReader(os.Args[1])
	e, err := elf.NewFile(f)
	if err != nil {
		panic(err)
	}

	// Read identifier
	var ident [16]uint8
	_, err = f.ReadAt(ident[0:], 0)
	if err != nil {
		panic(err)
	}

	// Decode identifier
	if ident[0] != '\x7f' ||
		ident[1] != 'E' ||
		ident[2] != 'L' ||
		ident[3] != 'F' {
		fmt.Printf("Bad magic number at %d\n", ident[0:4])
		os.Exit(1)
	}

	// Process by architecture
	sr := io.NewSectionReader(f, 0, 1<<63-1)
	var shoff, shentsize, shnum int64
	switch e.Class.String() {
	case "ELFCLASS64":
		hdr := new(elf.Header64)
		sr.Seek(0, 0)
		err = binary.Read(sr, e.ByteOrder, hdr)
		if err != nil {
			panic(err)
		}

		shoff = int64(hdr.Shoff)
		shnum = int64(hdr.Shnum)
		shentsize = int64(hdr.Shentsize)
	case "ELFCLASS32":
		hdr := new(elf.Header32)
		sr.Seek(0, 0)
		err = binary.Read(sr, e.ByteOrder, hdr)
		if err != nil {
			panic(err)
		}

		shoff = int64(hdr.Shoff)
		shnum = int64(hdr.Shnum)
		shentsize = int64(hdr.Shentsize)
	default:
		panic("unsupported elf architecture")
	}

	// calculate
	x := shoff + (shentsize * shnum)

	// Printout
	fmt.Printf("File Size                (ls -l)    :  %d\n", size)
	fmt.Printf("Start of section header  (shoff)    :  %d\n", shoff)
	fmt.Printf("Size of section header   (shentsize):  %d\n", shentsize)
	fmt.Printf("Number of section header (shnum)    :  %d\n", shnum)
	fmt.Printf("shoff + (shentsize * shnum)         :  %d\n", x)
}
// Output:
// File Size                (ls -l)    :  17384
// Start of section header  (shoff)    :  15464
// Size of section header   (shentsize):  64
// Number of section header (shnum)    :  30
// shoff + (shentsize * shnum)         :  17384

Note that there is no clear example in the package so I did a reverse engineering against NewFile(...). I do expect an easier way to get those values back since NewFile(...) by symbol query but again, limited by scarce documentation.

2 Likes

Thank you very much @hollowaykeanho. This is a very useful example and explanation. I owe you big time.

2 Likes

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.