Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

debug/macho - Mach-O 文件格式

概述

debug/macho 包提供了对 Mach-O(Mach Object)文件格式的读取支持。

Mach-O 是什么

  • 📋 macOS 标准格式:Apple 系统的可执行文件格式
  • 🔧 多种文件类型:可执行文件、动态库、静态库、目标文件
  • 📦 包含多个段和节:代码段、数据段、符号表、加载命令等
  • 🛠️ Apple 平台专用:用于 macOS、iOS、watchOS、tvOS

主要用途

  • 🔍 分析 macOS 可执行文件:读取段、节、符号信息
  • 🛠️ 链接器开发:处理目标文件和符号解析
  • 📊 二进制分析:提取程序结构信息
  • 🔐 安全工具:检查二进制文件完整性
  • 🐛 调试工具:配合调试器使用

重要说明

  • ⚠️ 只读访问:仅用于读取 Mach-O 文件
  • ⚠️ 底层格式:需要了解 Mach-O 规范
  • ⚠️ Apple 平台:主要用于 macOS/iOS 系统
  • 标准库支持:Go 标准库提供完整支持

Mach-O 文件结构

Mach-O 文件布局

+------------------+
| Mach Header      |  <- 文件头(32/64 位)
+------------------+
| Load Commands    |  <- 加载命令(段、库、符号等)
| (Variable Size)  |
+------------------+
| Segment 1        |  <- 各个段
|   Section 1      |
|   Section 2      |
| Segment 2        |
|   ...            |
+------------------+
| Symbol Table     |  <- 符号表(可选)
| String Table     |  <- 字符串表(可选)
+------------------+

加载命令类型

LC_SEGMENT/_64      - 定义内存段
LC_SYMTAB           - 符号表信息
LC_DYSYMTAB         - 动态符号表
LC_LOAD_DYLIB       - 加载动态库
LC_ID_DYLIB         - 动态库标识
LC_MAIN             - 主线程信息
LC_CODE_SIGNATURE   - 代码签名

核心类型

1. File - Mach-O 文件

type File struct {
    FileHeader
    Loads  []Load
    Sections []*Section
    Segments []*Segment
    
    // 包含过滤或未导出的字段
}

功能:表示打开的 Mach-O 文件。

字段说明

  • FileHeader:Mach-O 文件头
  • Loads:加载命令列表
  • Sections:节列表
  • Segments:段列表

主要方法

// 打开文件
func Open(name string) (*File, error)
func NewFile(r io.ReaderAt) (*File, error)

// 关闭文件
func (f *File) Close() error

// 获取节
func (f *File) Section(name string) *Section

// 获取符号
func (f *File) SymbolTable() (syms []Symbol, strtab []byte, err error)
func (f *File) DynamicSymbolTable() (extdef []Symbol, extrel []Reloc, localsym []Symbol, err error)

// 获取 DWARF 数据
func (f *File) DWARF() (*dwarf.Data, error)

// 获取导入的库
func (f *File) ImportedLibraries() ([]string, error)
func (f *File) ImportedSymbols() ([]string, error)

注意事项

  • ⚠️ Mach-O 文件可能有多个架构(Fat Binary)
  • ⚠️ 需要区分 32 位和 64 位格式
  • ✅ 提供完整的加载命令解析

2. FileHeader - 文件头

type FileHeader struct {
    Magic  uint32
    Cpu    Cpu
    SubCpu uint32
    Type   Type
    NCmd   uint32
    SizeOfCmds uint32
    Flags  uint32
}

字段说明

  • Magic:魔术数字(标识字节序和格式)
  • Cpu:CPU 类型
  • SubCpu:CPU 子类型
  • Type:文件类型
  • NCmd:加载命令数量
  • SizeOfCmds:加载命令总大小
  • Flags:文件标志

常见魔术数字

Magic32  = 0xfeedface  // 32 位小端
Magic64  = 0xfeedfacf  // 64 位小端
Magic32B = 0xcefaedfe  // 32 位大端
Magic64B = 0xcffaedfe  // 64 位大端

3. Section - 节

type Section struct {
    SectionHeader
    io.ReaderAt
}

功能:表示 Mach-O 文件中的一个节。

字段说明

  • SectionHeader:节头信息
  • io.ReaderAt:用于读取节内容

主要方法

// 读取节数据
func (s *Section) Data() ([]byte, error)

// 读取重定位
func (s *Section) Relocs() ([]Reloc, error)

// 读取符号
func (s *Section) Symbols() (syms []Symbol, err error)

4. SectionHeader - 节头

type SectionHeader struct {
    Name       string  // 节名称
    Seg        string  // 所属段名称
    Addr       uint64  // 内存地址
    Size       uint64  // 节大小
    Offset     uint32  // 文件偏移
    Align      uint32  // 对齐要求
    Reloff     uint32  // 重定位偏移
    Nreloc     uint32  // 重定位数量
    Flags      uint32  // 节标志
    Reserved1  uint32  // 保留
    Reserved2  uint32  // 保留
}

字段说明

  • Name:节名称(如 __text__data
  • Seg:所属段名称(如 __TEXT__DATA
  • Addr:加载到内存时的地址
  • Size:节大小
  • Offset:在文件中的偏移
  • Align:对齐要求
  • Flags:节标志(类型、属性等)

5. Segment - 段

type Segment struct {
    SegmentHeader
    io.ReaderAt
}

功能:表示 Mach-O 文件中的一个段。

字段说明

  • SegmentHeader:段头信息
  • io.ReaderAt:用于读取段内容

主要方法

// 读取段数据
func (s *Segment) Data() ([]byte, error)

6. SegmentHeader - 段头

type SegmentHeader struct {
    Name       string  // 段名称
    Addr       uint64  // 内存地址
    Memsz      uint64  // 内存大小
    Offset     uint64  // 文件偏移
    Filesz     uint64  // 文件大小
    Maxprot    uint32  // 最大保护
    Initprot   uint32  // 初始保护
    Nsect      uint32  // 节数量
    Flags      uint32  // 段标志
}

字段说明

  • Name:段名称(如 __TEXT__DATA__LINKEDIT
  • Addr:虚拟地址
  • Memsz:内存中的大小
  • Offset:文件偏移
  • Filesz:文件中的大小
  • Maxprot:最大保护标志(读/写/执行)
  • Initprot:初始保护标志
  • Nsect:包含的节数量
  • Flags:段标志

7. Load - 加载命令

type Load interface {
    // 加载命令接口
}

功能:表示 Mach-O 文件中的加载命令。

常见加载命令类型

// 段加载
LoadSegment32  // 32 位段
LoadSegment64  // 64 位段

// 动态库
LoadDylib      // 加载动态库
LoadWeakDylib  // 弱加载动态库
ReexportDylib  // 重新导出动态库
LoadUpwardDylib // 向上加载动态库

// 符号表
LoadSymtab     // 符号表
LoadDysymtab   // 动态符号表

// 程序信息
LoadMain       // 主线程信息
LoadUuid       // UUID 信息
LoadCodeSignature // 代码签名

8. Symbol - 符号

type Symbol struct {
    Name  string
    Type  byte
    Sect  int
    Desc  int
    Value uint64
}

字段说明

  • Name:符号名称
  • Type:符号类型(N_TYPE 掩码)
  • Sect:所在节索引
  • Desc:描述信息
  • Value:符号值(地址)

符号类型常量

N_UNDF = 0x0  // 未定义
N_ABS  = 0x2  // 绝对地址
N_SECT = 0xe  // 节内符号
N_PBUD = 0xc  // 预绑定符号

9. Reloc - 重定位

type Reloc struct {
    Addr uint64  // 地址
    Sym  int     // 符号索引
    Type int     // 重定位类型
    Size int     // 大小
    Pcrel bool   // PC 相对
}

常量定义

Magic - 魔术数字

const (
    Magic32  uint32 = 0xfeedface  // 32 位小端
    Magic64  uint32 = 0xfeedfacf  // 64 位小端
    Magic32B uint32 = 0xcefaedfe  // 32 位大端
    Magic64B uint32 = 0xcffaedfe  // 64 位大端
)

Type - 文件类型

const (
    TypeObj     Type = 0x1  // 目标文件
    TypeExecute Type = 0x2  // 可执行文件
    TypeFVMLib  Type = 0x3  // 固定地址动态库
    TypeCore    Type = 0x4  // 核心转储
    TypePexecute Type = 0x5 // 预加载可执行
    TypeFVMLibCore Type = 0x6
    TypeDsym    Type = 0x7  // DWARF 调试文件
    TypeKextBundle Type = 0x8 // 内核扩展
)

Cpu - CPU 类型

const (
    Cpu386     Cpu = 7   // x86
    CpuAmd64   Cpu = 0x01000007  // x86-64
    CpuArm     Cpu = 12  // ARM
    CpuArm64   Cpu = 0x0100000c  // ARM 64 位
    CpuPpc     Cpu = 18  // PowerPC
    CpuPpc64   Cpu = 0x01000012  // PowerPC 64 位
)

常见节名称

// __TEXT 段
__text          // 可执行代码
__const         // 常量数据
__cstring       // C 字符串
__stubs         // 桩代码
__stub_helper   // 桩辅助代码
__gcc_except_tab // GCC 异常表
__unwind_info   // 展开信息

// __DATA 段
__data          // 已初始化数据
__nl_symbol_ptr // 非懒加载符号指针
__la_symbol_ptr // 懒加载符号指针
__mod_init_func // 模块初始化函数
__mod_term_func // 模块终止函数

// __LINKEDIT 段
// 包含链接编辑信息(符号表、字符串表等)

// __DWARF 段
__debug_info    // DWARF 调试信息
__debug_abbrev  // DWARF 缩写表
__debug_line    // DWARF 行号信息
__debug_str     // DWARF 字符串表

完整示例

示例 1:打开和读取 Mach-O 文件

package main

import (
    "debug/macho"
    "fmt"
    "log"
)

func main() {
    // 1. 打开 Mach-O 文件
    f, err := macho.Open("myprogram")
    if err != nil {
        log.Fatal(err)
    }
    defer f.Close()
    
    // 2. 显示文件头信息
    fmt.Printf("魔术数字:0x%x\n", f.Magic)
    fmt.Printf("CPU 类型:%v\n", f.Cpu)
    fmt.Printf("CPU 子类型:0x%x\n", f.SubCpu)
    fmt.Printf("文件类型:%v\n", f.Type)
    fmt.Printf("加载命令数量:%d\n", f.NCmd)
    fmt.Printf("加载命令大小:%d 字节\n", f.SizeOfCmds)
    fmt.Printf("标志:0x%x\n", f.Flags)
    
    // 3. 显示节和段数量
    fmt.Printf("节数量:%d\n", len(f.Sections))
    fmt.Printf("段数量:%d\n", len(f.Segments))
    fmt.Printf("加载命令数量:%d\n", len(f.Loads))
}

示例 2:遍历所有节

package main

import (
    "debug/macho"
    "fmt"
    "log"
)

func main() {
    f, err := macho.Open("myprogram")
    if err != nil {
        log.Fatal(err)
    }
    defer f.Close()
    
    fmt.Println("Mach-O 节信息:")
    fmt.Println("=" + "=" * 79)
    
    for i, section := range f.Sections {
        fmt.Printf("%2d. 名称:%s\n", i, section.Name)
        fmt.Printf("    所属段:%s\n", section.Seg)
        fmt.Printf("    地址:0x%x\n", section.Addr)
        fmt.Printf("    偏移:0x%x\n", section.Offset)
        fmt.Printf("    大小:%d 字节\n", section.Size)
        fmt.Printf("    对齐:%d\n", section.Align)
        fmt.Printf("    标志:0x%x\n", section.Flags)
        
        if section.Nreloc > 0 {
            fmt.Printf("    重定位数量:%d\n", section.Nreloc)
        }
        
        fmt.Println()
    }
}

示例 3:读取特定节内容

package main

import (
    "debug/macho"
    "encoding/hex"
    "fmt"
    "log"
    "strings"
)

func main() {
    f, err := macho.Open("myprogram")
    if err != nil {
        log.Fatal(err)
    }
    defer f.Close()
    
    // 1. 读取 __text 节(代码)
    textSection := f.Section("__text")
    if textSection != nil {
        data, err := textSection.Data()
        if err != nil {
            log.Fatal(err)
        }
        
        fmt.Printf("__text 节:\n")
        fmt.Printf("  大小:%d 字节\n", len(data))
        fmt.Printf("  前 64 字节:%x\n", data[:64])
    }
    
    // 2. 读取 __cstring 节(C 字符串)
    cstringSection := f.Section("__cstring")
    if cstringSection != nil {
        data, err := cstringSection.Data()
        if err != nil {
            log.Fatal(err)
        }
        
        fmt.Printf("\n__cstring 节:\n")
        fmt.Printf("  大小:%d 字节\n", len(data))
        
        // 提取字符串
        strings := extractCString(data)
        fmt.Printf("  字符串数量:%d\n", len(strings))
        
        // 显示前 20 个字符串
        count := 0
        for _, str := range strings {
            if count >= 20 {
                break
            }
            if str != "" {
                fmt.Printf("    \"%s\"\n", str)
                count++
            }
        }
    }
    
    // 3. 读取 __data 节(已初始化数据)
    dataSection := f.Section("__data")
    if dataSection != nil {
        data, err := dataSection.Data()
        if err != nil {
            log.Fatal(err)
        }
        
        fmt.Printf("\n__data 节:\n")
        fmt.Printf("  大小:%d 字节\n", len(data))
    }
}

// extractCString 提取 C 风格字符串
func extractCString(data []byte) []string {
    var strings []string
    var current strings.Builder
    
    for _, b := range data {
        if b == 0 {
            if current.Len() > 0 {
                strings = append(strings, current.String())
                current.Reset()
            }
        } else {
            current.WriteByte(b)
        }
    }
    
    if current.Len() > 0 {
        strings = append(strings, current.String())
    }
    
    return strings
}

示例 4:读取符号表

package main

import (
    "debug/macho"
    "fmt"
    "log"
)

func main() {
    f, err := macho.Open("myprogram")
    if err != nil {
        log.Fatal(err)
    }
    defer f.Close()
    
    // 1. 读取符号表
    fmt.Println("符号表:")
    syms, strtab, err := f.SymbolTable()
    if err != nil {
        log.Printf("读取符号表失败:%v", err)
    } else {
        fmt.Printf("符号数量:%d\n", len(syms))
        fmt.Printf("字符串表大小:%d 字节\n", len(strtab))
        
        // 显示前 20 个符号
        count := 0
        for _, sym := range syms {
            if count >= 20 {
                break
            }
            
            // 跳过空符号
            if sym.Name == "" {
                continue
            }
            
            fmt.Printf("  %s\n", sym.Name)
            fmt.Printf("    类型:0x%x\n", sym.Type)
            fmt.Printf("    节:%d\n", sym.Sect)
            fmt.Printf("    值:0x%x\n", sym.Value)
            fmt.Printf("    描述:%d\n", sym.Desc)
            count++
        }
    }
    
    // 2. 读取动态符号表
    fmt.Println("\n动态符号表:")
    extdef, extrel, localsym, err := f.DynamicSymbolTable()
    if err != nil {
        log.Printf("读取动态符号表失败:%v", err)
    } else {
        fmt.Printf("外部定义符号:%d\n", len(extdef))
        fmt.Printf("外部重定位:%d\n", len(extrel))
        fmt.Printf("本地符号:%d\n", len(localsym))
        
        // 显示外部定义符号
        fmt.Println("\n外部定义符号:")
        for i, sym := range extdef {
            if i >= 20 {
                break
            }
            fmt.Printf("  %s (0x%x)\n", sym.Name, sym.Value)
        }
    }
}

示例 5:读取导入的库

package main

import (
    "debug/macho"
    "fmt"
    "log"
)

func main() {
    f, err := macho.Open("myprogram")
    if err != nil {
        log.Fatal(err)
    }
    defer f.Close()
    
    // 1. 读取导入的库
    fmt.Println("依赖的动态库:")
    libs, err := f.ImportedLibraries()
    if err != nil {
        log.Printf("读取库列表失败:%v", err)
    } else {
        for i, lib := range libs {
            fmt.Printf("  %2d. %s\n", i+1, lib)
        }
    }
    
    // 2. 读取导入的符号
    fmt.Println("\n导入的符号:")
    symbols, err := f.ImportedSymbols()
    if err != nil {
        log.Printf("读取导入符号失败:%v", err)
    } else {
        for i, sym := range symbols {
            if i >= 20 {
                break
            }
            fmt.Printf("  %s\n", sym)
        }
    }
    
    // 3. 遍历加载命令
    fmt.Println("\n加载命令:")
    for i, load := range f.Loads {
        fmt.Printf("  %2d. %T\n", i+1, load)
        
        // 根据类型显示详细信息
        switch l := load.(type) {
        case *macho.Dylib:
            fmt.Printf("       库:%s\n", l.Name)
        case *macho.Segment64:
            fmt.Printf("       段:%s (0x%x - 0x%x)\n", 
                l.Name, l.Addr, l.Addr+l.Memsz)
        case *macho.Main:
            fmt.Printf("       入口:0x%x, 栈大小:%d\n", 
                l.Entry, l.Stacksize)
        }
    }
}

示例 6:分析段信息

package main

import (
    "debug/macho"
    "fmt"
    "log"
)

func main() {
    f, err := macho.Open("myprogram")
    if err != nil {
        log.Fatal(err)
    }
    defer f.Close()
    
    fmt.Println("Mach-O 段信息:")
    fmt.Println("=" + "=" * 79)
    
    for i, segment := range f.Segments {
        fmt.Printf("%2d. 名称:%s\n", i, segment.Name)
        fmt.Printf("    虚拟地址:0x%x\n", segment.Addr)
        fmt.Printf("    内存大小:%d 字节\n", segment.Memsz)
        fmt.Printf("    文件大小:%d 字节\n", segment.Filesz)
        fmt.Printf("    文件偏移:0x%x\n", segment.Offset)
        fmt.Printf("    最大保护:0x%x\n", segment.Maxprot)
        fmt.Printf("    初始保护:0x%x\n", segment.Initprot)
        fmt.Printf("    节数量:%d\n", segment.Nsect)
        fmt.Printf("    标志:0x%x\n", segment.Flags)
        
        // 读取段内容(如果存在)
        if segment.Filesz > 0 {
            data, err := segment.Data()
            if err == nil && len(data) > 0 {
                fmt.Printf("    内容预览:%x\n", data[:min(32, len(data))])
            }
        }
        
        // 显示段中的节
        fmt.Printf("    包含的节:\n")
        for _, section := range f.Sections {
            if section.Seg == segment.Name {
                fmt.Printf("      - %s (%d 字节)\n", section.Name, section.Size)
            }
        }
        
        fmt.Println()
    }
}

func min(a, b int) int {
    if a < b {
        return a
    }
    return b
}

示例 7:Mach-O 文件分析工具

package main

import (
    "debug/macho"
    "encoding/hex"
    "fmt"
    "io"
    "log"
    "os"
    "strings"
)

// MachOAnalyzer Mach-O 文件分析器
type MachOAnalyzer struct {
    file *macho.File
}

// NewMachOAnalyzer 创建分析器
func NewMachOAnalyzer(filename string) (*MachOAnalyzer, error) {
    f, err := macho.Open(filename)
    if err != nil {
        return nil, err
    }
    
    return &MachOAnalyzer{file: f}, nil
}

// Close 关闭文件
func (a *MachOAnalyzer) Close() error {
    return a.file.Close()
}

// ShowHeader 显示文件头
func (a *MachOAnalyzer) ShowHeader() {
    f := a.file
    
    fmt.Println("=== Mach-O 文件头 ===")
    fmt.Printf("魔术数字:0x%x\n", f.Magic)
    fmt.Printf("CPU 类型:%v\n", f.Cpu)
    fmt.Printf("CPU 子类型:0x%x\n", f.SubCpu)
    fmt.Printf("文件类型:%v\n", f.Type)
    fmt.Printf("加载命令数量:%d\n", f.NCmd)
    fmt.Printf("加载命令大小:%d 字节\n", f.SizeOfCmds)
    fmt.Printf("标志:0x%x\n", f.Flags)
    fmt.Println()
}

// ShowSections 显示节信息
func (a *MachOAnalyzer) ShowSections() {
    fmt.Println("=== Mach-O 节 ===")
    
    for i, section := range a.file.Sections {
        fmt.Printf("%2d. %-20s 段:%-10s 大小:%6d 字节", 
            i, section.Name, section.Seg, section.Size)
        
        if section.Addr != 0 {
            fmt.Printf("  地址:0x%x", section.Addr)
        }
        
        fmt.Println()
    }
    fmt.Println()
}

// ShowSegments 显示段信息
func (a *MachOAnalyzer) ShowSegments() {
    fmt.Println("=== Mach-O 段 ===")
    
    for i, segment := range a.file.Segments {
        fmt.Printf("%2d. %-15s 大小:%6d 字节  保护:%s", 
            i, segment.Name, segment.Memsz, 
            protectionString(segment.Initprot))
        
        if segment.Filesz > 0 {
            fmt.Printf("  文件:%6d 字节", segment.Filesz)
        }
        
        fmt.Println()
    }
    fmt.Println()
}

// ShowSymbols 显示符号
func (a *MachOAnalyzer) ShowSymbols() {
    fmt.Println("=== 符号表 ===")
    
    syms, _, err := a.file.SymbolTable()
    if err != nil {
        fmt.Printf("读取符号失败:%v\n", err)
        return
    }
    
    fmt.Printf("符号数量:%d\n\n", len(syms))
    
    // 按类型分组
    funcs := make([]macho.Symbol, 0)
    objects := make([]macho.Symbol, 0)
    others := make([]macho.Symbol, 0)
    
    for _, sym := range syms {
        if sym.Name == "" {
            continue
        }
        
        switch sym.Type & 0xe {
        case 0xe: // N_SECT
            if strings.HasPrefix(sym.Name, "_") {
                funcs = append(funcs, sym)
            } else {
                objects = append(objects, sym)
            }
        default:
            others = append(others, sym)
        }
    }
    
    fmt.Printf("函数:%d 个\n", len(funcs))
    fmt.Printf("数据对象:%d 个\n", len(objects))
    fmt.Printf("其他:%d 个\n\n", len(others))
    
    // 显示前 10 个函数
    if len(funcs) > 0 {
        fmt.Println("函数示例:")
        for i, sym := range funcs {
            if i >= 10 {
                break
            }
            fmt.Printf("  %-40s 0x%x\n", sym.Name, sym.Value)
        }
        fmt.Println()
    }
}

// ShowLibraries 显示库依赖
func (a *MachOAnalyzer) ShowLibraries() {
    fmt.Println("=== 依赖库 ===")
    
    libs, err := a.file.ImportedLibraries()
    if err != nil {
        fmt.Printf("读取库失败:%v\n", err)
        return
    }
    
    for _, lib := range libs {
        fmt.Printf("  %s\n", lib)
    }
    fmt.Println()
}

// ShowLoadCommands 显示加载命令
func (a *MachOAnalyzer) ShowLoadCommands() {
    fmt.Println("=== 加载命令 ===")
    
    for i, load := range a.file.Loads {
        fmt.Printf("%2d. %T\n", i, load)
        
        // 根据类型显示详细信息
        switch l := load.(type) {
        case *macho.Dylib:
            fmt.Printf("    库:%s\n", l.Name)
        case *macho.Segment64:
            fmt.Printf("    段:%s (0x%x - 0x%x)\n", 
                l.Name, l.Addr, l.Addr+l.Memsz)
        case *macho.Main:
            fmt.Printf("    入口:0x%x, 栈大小:%d\n", 
                l.Entry, l.Stacksize)
        case *macho.Uuid:
            fmt.Printf("    UUID: %x-%x-%x-%x\n", 
                l.Id[0:4], l.Id[4:6], l.Id[6:8], l.Id[8:16])
        }
    }
    fmt.Println()
}

// FindSymbol 查找符号
func (a *MachOAnalyzer) FindSymbol(pattern string) error {
    syms, _, err := a.file.SymbolTable()
    if err != nil {
        return err
    }
    
    count := 0
    for _, sym := range syms {
        if strings.Contains(sym.Name, pattern) {
            fmt.Printf("找到符号:%s\n", sym.Name)
            fmt.Printf("  类型:0x%x\n", sym.Type)
            fmt.Printf("  节:%d\n", sym.Sect)
            fmt.Printf("  值:0x%x\n", sym.Value)
            fmt.Printf("  描述:%d\n", sym.Desc)
            fmt.Println()
            count++
            
            if count >= 20 {
                break
            }
        }
    }
    
    if count == 0 {
        fmt.Printf("未找到匹配的符号\n")
    } else {
        fmt.Printf("找到 %d 个匹配符号\n", count)
    }
    
    return nil
}

// DumpSection 转储节内容
func (a *MachOAnalyzer) DumpSection(name string, output string) error {
    section := a.file.Section(name)
    if section == nil {
        return fmt.Errorf("未找到节:%s", name)
    }
    
    data, err := section.Data()
    if err != nil {
        return err
    }
    
    file, err := os.Create(output)
    if err != nil {
        return err
    }
    defer file.Close()
    
    _, err = file.Write(data)
    if err != nil {
        return err
    }
    
    fmt.Printf("已将 %s 节保存到 %s (%d 字节)\n", name, output, len(data))
    return nil
}

// protectionString 将保护标志转换为字符串
func protectionString(prot uint32) string {
    var s strings.Builder
    if prot&0x1 != 0 {
        s.WriteString("r")
    }
    if prot&0x2 != 0 {
        s.WriteString("w")
    }
    if prot&0x4 != 0 {
        s.WriteString("x")
    }
    if s.Len() == 0 {
        return "---"
    }
    return s.String()
}

func main() {
    if len(os.Args) < 2 {
        log.Fatal("用法:macho-analyzer <macho-file> [command]")
    }
    
    filename := os.Args[1]
    
    analyzer, err := NewMachOAnalyzer(filename)
    if err != nil {
        log.Fatal(err)
    }
    defer analyzer.Close()
    
    if len(os.Args) > 2 {
        command := os.Args[2]
        
        switch command {
        case "header":
            analyzer.ShowHeader()
        case "sections":
            analyzer.ShowSections()
        case "segments":
            analyzer.ShowSegments()
        case "symbols":
            analyzer.ShowSymbols()
        case "libs":
            analyzer.ShowLibraries()
        case "loads":
            analyzer.ShowLoadCommands()
        case "find":
            if len(os.Args) > 3 {
                analyzer.FindSymbol(os.Args[3])
            }
        case "dump":
            if len(os.Args) > 4 {
                err := analyzer.DumpSection(os.Args[3], os.Args[4])
                if err != nil {
                    log.Fatal(err)
                }
            }
        default:
            // 显示所有信息
            analyzer.ShowHeader()
            analyzer.ShowSections()
            analyzer.ShowSegments()
            analyzer.ShowSymbols()
            analyzer.ShowLibraries()
            analyzer.ShowLoadCommands()
        }
    } else {
        // 默认显示所有信息
        analyzer.ShowHeader()
        analyzer.ShowSections()
        analyzer.ShowSegments()
        analyzer.ShowSymbols()
        analyzer.ShowLibraries()
        analyzer.ShowLoadCommands()
    }
}

示例 8:读取 DWARF 调试信息

package main

import (
    "debug/macho"
    "debug/dwarf"
    "fmt"
    "io"
    "log"
)

func main() {
    f, err := macho.Open("myprogram")
    if err != nil {
        log.Fatal(err)
    }
    defer f.Close()
    
    // 读取 DWARF 数据
    data, err := f.DWARF()
    if err != nil {
        log.Fatal("无 DWARF 信息:", err)
    }
    
    // 创建读取器
    reader := data.Reader()
    
    // 遍历 DWARF 条目
    fmt.Println("DWARF 调试信息:")
    
    for {
        entry, err := reader.Next()
        if err == io.EOF {
            break
        }
        if err != nil {
            log.Fatal(err)
        }
        
        // 查找函数
        if entry.Tag == dwarf.TagSubprogram {
            name := entry.Val(dwarf.AttrName)
            if name != nil {
                fmt.Printf("函数:%v\n", name)
                
                lowpc := entry.Val(dwarf.AttrLowpc)
                highpc := entry.Val(dwarf.AttrHighpc)
                if lowpc != nil && highpc != nil {
                    fmt.Printf("  地址范围:0x%x - 0x%x\n", lowpc, highpc)
                }
            }
        }
        
        if entry.Children {
            reader.SkipChildren()
        }
    }
}

示例 9:Fat Binary(通用二进制)支持

package main

import (
    "debug/macho"
    "fmt"
    "log"
)

// FatBinary 分析 Fat Binary(包含多个架构)
func analyzeFatBinary(filename string) error {
    f, err := macho.OpenFat(filename)
    if err != nil {
        // 不是 Fat Binary,作为普通 Mach-O 处理
        return analyzeSingleBinary(filename)
    }
    defer f.Close()
    
    fmt.Printf("Fat Binary:包含 %d 个架构\n\n", len(f.Arches))
    
    for i, arch := range f.Arches {
        fmt.Printf("架构 %d:\n", i+1)
        fmt.Printf("  CPU: %v (子类型:0x%x)\n", arch.Cpu, arch.SubCpu)
        fmt.Printf("  偏移:0x%x\n", arch.Offset)
        fmt.Printf("  大小:%d 字节\n", arch.Size)
        
        // 分析这个架构
        mf, err := arch.Open()
        if err != nil {
            log.Printf("  打开失败:%v\n", err)
            continue
        }
        
        fmt.Printf("  文件类型:%v\n", mf.Type)
        fmt.Printf("  节数量:%d\n", len(mf.Sections))
        fmt.Printf("  段数量:%d\n", len(mf.Segments))
        
        mf.Close()
        fmt.Println()
    }
    
    return nil
}

// analyzeSingleBinary 分析单个 Mach-O 文件
func analyzeSingleBinary(filename string) error {
    f, err := macho.Open(filename)
    if err != nil {
        return err
    }
    defer f.Close()
    
    fmt.Println("单架构 Mach-O 文件")
    fmt.Printf("CPU: %v (子类型:0x%x)\n", f.Cpu, f.SubCpu)
    fmt.Printf("文件类型:%v\n", f.Type)
    fmt.Printf("节数量:%d\n", len(f.Sections))
    fmt.Printf("段数量:%d\n", len(f.Segments))
    
    return nil
}

func main() {
    if len(os.Args) < 2 {
        log.Fatal("用法:fat-analyzer <macho-file>")
    }
    
    err := analyzeFatBinary(os.Args[1])
    if err != nil {
        log.Fatal(err)
    }
}

安全最佳实践

✅ 推荐做法

  1. 始终检查错误

    f, err := macho.Open("file")
    if err != nil {
        return err
    }
    defer f.Close()
    
  2. 检查 nil 指针

    section := f.Section("__text")
    if section != nil {
        data, _ := section.Data()
    }
    
  3. 验证数据大小

    data, err := section.Data()
    if err != nil {
        return err
    }
    
    if len(data) < expectedSize {
        return fmt.Errorf("数据太小")
    }
    
  4. 处理 Fat Binary

    f, err := macho.OpenFat(filename)
    if err != nil {
        // 回退到单架构处理
    }
    

❌ 不安全做法

  1. 不要忽略错误

    // ❌ 错误
    f, _ := macho.Open("file")
    
    // ✅ 正确
    f, err := macho.Open("file")
    if err != nil {
        // 处理错误
    }
    
  2. 不要忘记关闭文件

    // ❌ 错误
    f, _ := macho.Open("file")
    
    // ✅ 正确
    f, _ := macho.Open("file")
    defer f.Close()
    
  3. 不要假设节一定存在

    // ❌ 错误
    data, _ := f.Section("__text").Data()
    
    // ✅ 正确
    section := f.Section("__text")
    if section == nil {
        return error
    }
    data, err := section.Data()
    

总结

核心类型

File         // Mach-O 文件
FileHeader   // 文件头
Section      // 节
SectionHeader // 节头
Segment      // 段
SegmentHeader // 段头
Load         // 加载命令
Symbol       // 符号
Reloc        // 重定位

使用场景

场景推荐方法说明
打开文件macho.Open()读取 Mach-O 文件
打开 Fatmacho.OpenFat()读取通用二进制
读取节File.Section()获取特定节
读取符号File.SymbolTable()获取符号表
获取库依赖File.ImportedLibraries()获取依赖库
读取 DWARFFile.DWARF()获取调试信息

Mach-O 文件类型

类型常量说明
目标文件TypeObj.o 文件
可执行文件TypeExecute可执行程序
动态库TypeFVMLib.dylib 文件
核心转储TypeCore核心文件
调试文件TypeDsym.dSYM 文件

常见 CPU 类型

架构常量说明
x86Cpu38632 位 Intel
x86-64CpuAmd6464 位 Intel
ARMCpuArm32 位 ARM
ARM64CpuArm6464 位 ARM

常见段

段名用途
__TEXT代码和只读数据
__DATA已初始化数据
__LINKEDIT链接编辑信息
__DWARF调试信息

常见节

节名用途
__text__TEXT可执行代码
__const__TEXT常量数据
__cstring__TEXTC 字符串
__data__DATA已初始化数据
__nl_symbol_ptr__DATA非懒加载指针
__la_symbol_ptr__DATA懒加载指针

参考资料


最后更新:2026-04-03
Go 版本:Go 1.23+