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)
}
}
安全最佳实践
✅ 推荐做法
-
始终检查错误
f, err := macho.Open("file") if err != nil { return err } defer f.Close() -
检查 nil 指针
section := f.Section("__text") if section != nil { data, _ := section.Data() } -
验证数据大小
data, err := section.Data() if err != nil { return err } if len(data) < expectedSize { return fmt.Errorf("数据太小") } -
处理 Fat Binary
f, err := macho.OpenFat(filename) if err != nil { // 回退到单架构处理 }
❌ 不安全做法
-
不要忽略错误
// ❌ 错误 f, _ := macho.Open("file") // ✅ 正确 f, err := macho.Open("file") if err != nil { // 处理错误 } -
不要忘记关闭文件
// ❌ 错误 f, _ := macho.Open("file") // ✅ 正确 f, _ := macho.Open("file") defer f.Close() -
不要假设节一定存在
// ❌ 错误 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 文件 |
| 打开 Fat | macho.OpenFat() | 读取通用二进制 |
| 读取节 | File.Section() | 获取特定节 |
| 读取符号 | File.SymbolTable() | 获取符号表 |
| 获取库依赖 | File.ImportedLibraries() | 获取依赖库 |
| 读取 DWARF | File.DWARF() | 获取调试信息 |
Mach-O 文件类型
| 类型 | 常量 | 说明 |
|---|---|---|
| 目标文件 | TypeObj | .o 文件 |
| 可执行文件 | TypeExecute | 可执行程序 |
| 动态库 | TypeFVMLib | .dylib 文件 |
| 核心转储 | TypeCore | 核心文件 |
| 调试文件 | TypeDsym | .dSYM 文件 |
常见 CPU 类型
| 架构 | 常量 | 说明 |
|---|---|---|
| x86 | Cpu386 | 32 位 Intel |
| x86-64 | CpuAmd64 | 64 位 Intel |
| ARM | CpuArm | 32 位 ARM |
| ARM64 | CpuArm64 | 64 位 ARM |
常见段
| 段名 | 用途 |
|---|---|
__TEXT | 代码和只读数据 |
__DATA | 已初始化数据 |
__LINKEDIT | 链接编辑信息 |
__DWARF | 调试信息 |
常见节
| 节名 | 段 | 用途 |
|---|---|---|
__text | __TEXT | 可执行代码 |
__const | __TEXT | 常量数据 |
__cstring | __TEXT | C 字符串 |
__data | __DATA | 已初始化数据 |
__nl_symbol_ptr | __DATA | 非懒加载指针 |
__la_symbol_ptr | __DATA | 懒加载指针 |
参考资料
最后更新:2026-04-03
Go 版本:Go 1.23+