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()