From c23a5b65ef1a5255a42ada8acda7ff506e6ea075 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Sun, 22 May 2022 16:42:52 +0200 Subject: [PATCH] darwin: support -size= flag This is just basic support. It doesn't add support for reading DWARF, because that's a bit complicated on MacOS (it isn't stored in the file itself but separately in the object files). But at least this change makes it possible to easily print executable sizes by section type like for other operating systems. --- Makefile | 4 ++-- builder/sizes.go | 53 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 55 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index cd267dbd..fe476269 100644 --- a/Makefile +++ b/Makefile @@ -634,8 +634,8 @@ endif @$(MD5SUM) test.hex GOOS=linux GOARCH=arm $(TINYGO) build -size short -o test.elf ./testdata/cgo GOOS=windows GOARCH=amd64 $(TINYGO) build -size short -o test.exe ./testdata/cgo - GOOS=darwin GOARCH=amd64 $(TINYGO) build -o test ./testdata/cgo - GOOS=darwin GOARCH=arm64 $(TINYGO) build -o test ./testdata/cgo + GOOS=darwin GOARCH=amd64 $(TINYGO) build -size short -o test ./testdata/cgo + GOOS=darwin GOARCH=arm64 $(TINYGO) build -size short -o test ./testdata/cgo ifneq ($(OS),Windows_NT) # TODO: this does not yet work on Windows. Somehow, unused functions are # not garbage collected. diff --git a/builder/sizes.go b/builder/sizes.go index f3ffc2f1..d9e430f1 100644 --- a/builder/sizes.go +++ b/builder/sizes.go @@ -4,6 +4,7 @@ import ( "bytes" "debug/dwarf" "debug/elf" + "debug/macho" "debug/pe" "encoding/binary" "fmt" @@ -368,6 +369,58 @@ func loadProgramSize(path string, packagePathMap map[string]string) (*programSiz }) } } + } else if file, err := macho.NewFile(f); err == nil { + // TODO: read DWARF information. On MacOS, DWARF debug information isn't + // stored in the executable but stays in the object files. The + // executable does however contain the object file paths that contain + // debug information. + + // Read segments, for use while reading through sections. + segments := map[string]*macho.Segment{} + for _, load := range file.Loads { + switch load := load.(type) { + case *macho.Segment: + segments[load.Name] = load + } + } + + // Read MachO sections. + for _, section := range file.Sections { + sectionType := section.Flags & 0xff + sectionFlags := section.Flags >> 8 + segment := segments[section.Seg] + // For the constants used here, see: + // https://github.com/llvm/llvm-project/blob/release/14.x/llvm/include/llvm/BinaryFormat/MachO.h + if sectionFlags&0x800000 != 0 { // S_ATTR_PURE_INSTRUCTIONS + // Section containing only instructions. + sections = append(sections, memorySection{ + Address: section.Addr, + Size: uint64(section.Size), + Type: memoryCode, + }) + } else if sectionType == 1 { // S_ZEROFILL + // Section filled with zeroes on demand. + sections = append(sections, memorySection{ + Address: section.Addr, + Size: uint64(section.Size), + Type: memoryBSS, + }) + } else if segment.Maxprot&0b011 == 0b001 { // --r (read-only data) + // Protection doesn't allow writes, so mark this section read-only. + sections = append(sections, memorySection{ + Address: section.Addr, + Size: uint64(section.Size), + Type: memoryROData, + }) + } else { + // The rest is assumed to be regular data. + sections = append(sections, memorySection{ + Address: section.Addr, + Size: uint64(section.Size), + Type: memoryData, + }) + } + } } else if file, err := pe.NewFile(f); err == nil { // Read DWARF information. The error is intentionally ignored. data, _ := file.DWARF()