debug/plan9obj - Plan 9 对象文件格式
概述
debug/plan9obj 包提供了对 Plan 9 对象文件格式的读取支持。
Plan 9 对象文件是什么:
- 📋 Plan 9 标准格式:Plan 9 操作系统的对象文件格式
- 🔧 简洁设计:相比 ELF/Mach-O 更简单的对象文件格式
- 📦 Go 编译器使用:Go 编译器早期版本使用的对象文件格式
- 🛠️ 历史意义:了解 Go 工具链演变的重要参考
主要用途:
- 🔍 分析 Plan 9 对象文件:读取节、符号信息
- 🛠️ 链接器开发:处理 Plan 9 格式的目标文件
- 📊 二进制分析:提取程序结构信息
- 🐛 调试工具:配合调试器使用
- 📚 学习参考:理解对象文件格式设计
重要说明:
- ⚠️ 只读访问:仅用于读取 Plan 9 对象文件
- ⚠️ 历史格式:现代 Go 版本已使用其他格式
- ⚠️ 特定平台:主要用于 Plan 9 系统
- ✅ 标准库支持:Go 标准库保留支持
与其他格式的比较:
- ELF:Unix/Linux 标准,功能完整但复杂
- Mach-O:macOS/iOS 标准,结构清晰
- PE:Windows 标准,兼容性好
- Plan 9:简洁设计,教学价值高
Plan 9 对象文件结构
文件布局
+------------------+
| File Header | <- 文件头(固定大小)
+------------------+
| Section Headers | <- 节头表
+------------------+
| Section Data | <- 各个节的数据
+------------------+
| Symbol Table | <- 符号表
+------------------+
| String Table | <- 字符串表
+------------------+
文件头结构
Magic: 4 bytes - 魔术数字(标识文件类型)
Bss: 4 bytes - BSS 段大小
Entry: 4 bytes - 入口点地址
节头结构
Name: 4 bytes - 节名称偏移
Type: 1 byte - 节类型
Flags: 1 byte - 节标志
Addr: 4 bytes - 内存地址
Size: 4 bytes - 节大小
Offset: 4 bytes - 文件偏移
核心类型
1. File - Plan 9 对象文件
type File struct {
FileHeader
Sections []*Section
Symbols []Symbol
// 包含过滤或未导出的字段
}
功能:表示打开的 Plan 9 对象文件。
字段说明:
FileHeader:Plan 9 文件头Sections:节列表Symbols:符号列表
主要方法:
// 打开文件
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) Symbols() ([]Symbol, error)
// 获取字符串
func (f *File) StringTable() ([]byte, error)
注意事项:
- ⚠️ Plan 9 对象文件相对简单
- ⚠️ 符号表信息可能有限
- ✅ 提供基础的节和符号访问
2. FileHeader - 文件头
type FileHeader struct {
Magic uint32
Bss uint32
Entry uint64
}
字段说明:
Magic:魔术数字(标识文件类型和字节序)Bss:BSS 段(未初始化数据)大小Entry:程序入口点地址
常见魔术数字:
Magic32 = 0x00008000 // 32 位小端
Magic64 = 0x00008001 // 64 位小端
3. Section - 节
type Section struct {
SectionHeader
io.ReaderAt
}
功能:表示 Plan 9 对象文件中的一个节。
字段说明:
SectionHeader:节头信息io.ReaderAt:用于读取节内容
主要方法:
// 读取节数据
func (s *Section) Data() ([]byte, error)
// 读取重定位
func (s *Section) Relocs() ([]Reloc, error)
4. SectionHeader - 节头
type SectionHeader struct {
Name string
Type SectionType
Flags SectionFlag
Addr uint64
Size uint64
Offset uint64
}
字段说明:
Name:节名称Type:节类型(代码、数据等)Flags:节标志(可读、可写、可执行等)Addr:内存地址Size:节大小Offset:文件偏移
5. Symbol - 符号
type Symbol struct {
Name string
Type SymType
Value uint64
Size uint64
}
字段说明:
Name:符号名称Type:符号类型(函数、数据等)Value:符号值(地址)Size:符号大小
符号类型:
STypeText // 代码
STypeData // 数据
STypeBSS // BSS
STypeCommon // 公共符号
6. Reloc - 重定位
type Reloc struct {
Offset uint64
Sym int
Type int
Addend int64
}
字段说明:
Offset:重定位偏移Sym:符号索引Type:重定位类型Addend:加数
常量定义
Magic - 魔术数字
const (
Magic32 uint32 = 0x00008000 // 32 位
Magic64 uint32 = 0x00008001 // 64 位
)
SectionType - 节类型
const (
TypeNull SectionType = iota // 无效
TypeText // 代码段
TypeData // 数据段
TypeBSS // BSS 段
TypeString // 字符串表
TypeSymbol // 符号表
)
SectionFlag - 节标志
const (
FlagNone SectionFlag = 0x00
FlagAlloc SectionFlag = 0x01 // 分配内存
FlagWrite SectionFlag = 0x02 // 可写
FlagExec SectionFlag = 0x04 // 可执行
FlagLoad SectionFlag = 0x08 // 可加载
)
SymType - 符号类型
const (
SymTypeNone SymType = iota // 无类型
SymTypeText // 代码
SymTypeData // 数据
SymTypeBSS // BSS
SymTypeCommon // 公共符号
)
常见节名称
.text // 代码节
.data // 数据节
.bss // BSS 节
.symtab // 符号表
.strtab // 字符串表
.rodata // 只读数据节
完整示例
示例 1:打开和读取 Plan 9 对象文件
package main
import (
"debug/plan9obj"
"fmt"
"log"
)
func main() {
// 1. 打开 Plan 9 对象文件
f, err := plan9obj.Open("myprogram.9")
if err != nil {
log.Fatal(err)
}
defer f.Close()
// 2. 显示文件头信息
fmt.Printf("魔术数字:0x%x\n", f.Magic)
fmt.Printf("BSS 大小:%d 字节\n", f.Bss)
fmt.Printf("入口点:0x%x\n", f.Entry)
// 3. 显示节和符号数量
fmt.Printf("节数量:%d\n", len(f.Sections))
fmt.Printf("符号数量:%d\n", len(f.Symbols))
}
示例 2:遍历所有节
package main
import (
"debug/plan9obj"
"fmt"
"log"
)
func main() {
f, err := plan9obj.Open("myprogram.9")
if err != nil {
log.Fatal(err)
}
defer f.Close()
fmt.Println("Plan 9 节信息:")
fmt.Println("=" + "=" * 79)
for i, section := range f.Sections {
fmt.Printf("%2d. 名称:%s\n", i, section.Name)
fmt.Printf(" 类型:%v\n", section.Type)
fmt.Printf(" 标志:%v\n", section.Flags)
fmt.Printf(" 地址:0x%x\n", section.Addr)
fmt.Printf(" 大小:%d 字节\n", section.Size)
fmt.Printf(" 偏移:0x%x\n", section.Offset)
fmt.Println()
}
}
示例 3:读取特定节内容
package main
import (
"debug/plan9obj"
"encoding/hex"
"fmt"
"log"
)
func main() {
f, err := plan9obj.Open("myprogram.9")
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. 读取 .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))
}
// 3. 读取 .rodata 节(只读数据)
rodataSection := f.Section(".rodata")
if rodataSection != nil {
data, err := rodataSection.Data()
if err != nil {
log.Fatal(err)
}
fmt.Printf("\n.rodata 节:\n")
fmt.Printf(" 大小:%d 字节\n", len(data))
}
}
示例 4:读取符号表
package main
import (
"debug/plan9obj"
"fmt"
"log"
)
func main() {
f, err := plan9obj.Open("myprogram.9")
if err != nil {
log.Fatal(err)
}
defer f.Close()
// 1. 读取符号表
fmt.Println("符号表:")
symbols, err := f.Symbols()
if err != nil {
log.Printf("读取符号表失败:%v", err)
} else {
fmt.Printf("符号数量:%d\n", len(symbols))
// 显示前 20 个符号
count := 0
for _, sym := range symbols {
if count >= 20 {
break
}
// 跳过空符号
if sym.Name == "" {
continue
}
fmt.Printf(" %s\n", sym.Name)
fmt.Printf(" 类型:%v\n", sym.Type)
fmt.Printf(" 值:0x%x\n", sym.Value)
fmt.Printf(" 大小:%d\n", sym.Size)
count++
}
}
// 2. 按类型分组显示
fmt.Println("\n按类型分组:")
textSyms := make([]plan9obj.Symbol, 0)
dataSyms := make([]plan9obj.Symbol, 0)
bssSyms := make([]plan9obj.Symbol, 0)
for _, sym := range symbols {
switch sym.Type {
case plan9obj.SymTypeText:
textSyms = append(textSyms, sym)
case plan9obj.SymTypeData:
dataSyms = append(dataSyms, sym)
case plan9obj.SymTypeBSS:
bssSyms = append(bssSyms, sym)
}
}
fmt.Printf("代码符号:%d\n", len(textSyms))
fmt.Printf("数据符号:%d\n", len(dataSyms))
fmt.Printf("BSS 符号:%d\n", len(bssSyms))
}
示例 5:分析文件结构
package main
import (
"debug/plan9obj"
"fmt"
"log"
)
func main() {
f, err := plan9obj.Open("myprogram.9")
if err != nil {
log.Fatal(err)
}
defer f.Close()
fmt.Println("=== Plan 9 对象文件分析 ===\n")
// 1. 文件头信息
fmt.Println("文件头信息:")
fmt.Printf(" 魔术数字:0x%x\n", f.Magic)
fmt.Printf(" 格式:%s\n", formatString(f.Magic))
fmt.Printf(" BSS 大小:%d 字节\n", f.Bss)
fmt.Printf(" 入口点:0x%x\n\n", f.Entry)
// 2. 节统计
fmt.Println("节统计:")
var textSize, dataSize, bssSize uint64
for _, section := range f.Sections {
switch section.Type {
case plan9obj.TypeText:
textSize += section.Size
case plan9obj.TypeData:
dataSize += section.Size
case plan9obj.TypeBSS:
bssSize += section.Size
}
fmt.Printf(" %s: %d 字节\n", section.Name, section.Size)
}
fmt.Printf("\n总计:\n")
fmt.Printf(" 代码段:%d 字节\n", textSize)
fmt.Printf(" 数据段:%d 字节\n", dataSize)
fmt.Printf(" BSS 段:%d 字节\n", bssSize)
fmt.Printf(" 总大小:%d 字节\n\n", textSize+dataSize+bssSize)
// 3. 符号统计
fmt.Println("符号统计:")
symbols, _ := f.Symbols()
textCount := 0
dataCount := 0
bssCount := 0
for _, sym := range symbols {
switch sym.Type {
case plan9obj.SymTypeText:
textCount++
case plan9obj.SymTypeData:
dataCount++
case plan9obj.SymTypeBSS:
bssCount++
}
}
fmt.Printf(" 总符号数:%d\n", len(symbols))
fmt.Printf(" 代码符号:%d\n", textCount)
fmt.Printf(" 数据符号:%d\n", dataCount)
fmt.Printf(" BSS 符号:%d\n", bssCount)
}
// formatString 将魔术数字转换为格式字符串
func formatString(magic uint32) string {
switch magic {
case plan9obj.Magic32:
return "32 位"
case plan9obj.Magic64:
return "64 位"
default:
return "未知"
}
}
示例 6:查找特定符号
package main
import (
"debug/plan9obj"
"fmt"
"log"
"strings"
)
// SymbolFinder 符号查找器
type SymbolFinder struct {
file *plan9obj.File
}
// NewSymbolFinder 创建查找器
func NewSymbolFinder(filename string) (*SymbolFinder, error) {
f, err := plan9obj.Open(filename)
if err != nil {
return nil, err
}
return &SymbolFinder{file: f}, nil
}
// Close 关闭文件
func (f *SymbolFinder) Close() error {
return f.file.Close()
}
// FindByName 按名称查找符号
func (f *SymbolFinder) FindByName(pattern string) []plan9obj.Symbol {
symbols, _ := f.file.Symbols()
var results []plan9obj.Symbol
for _, sym := range symbols {
if strings.Contains(sym.Name, pattern) {
results = append(results, sym)
}
}
return results
}
// FindByType 按类型查找符号
func (f *SymbolFinder) FindByType(typ plan9obj.SymType) []plan9obj.Symbol {
symbols, _ := f.file.Symbols()
var results []plan9obj.Symbol
for _, sym := range symbols {
if sym.Type == typ {
results = append(results, sym)
}
}
return results
}
// FindMain 查找 main 函数
func (f *SymbolFinder) FindMain() *plan9obj.Symbol {
symbols, _ := f.file.Symbols()
for _, sym := range symbols {
if sym.Name == "main" || sym.Name == "_main" {
return &sym
}
}
return nil
}
// ListFunctions 列出所有函数
func (f *SymbolFinder) ListFunctions() []plan9obj.Symbol {
return f.FindByType(plan9obj.SymTypeText)
}
// ListVariables 列出所有变量
func (f *SymbolFinder) ListVariables() []plan9obj.Symbol {
return f.FindByType(plan9obj.SymTypeData)
}
func main() {
if len(os.Args) < 2 {
log.Fatal("用法:symfinder <plan9-file> [command] [pattern]")
}
filename := os.Args[1]
finder, err := NewSymbolFinder(filename)
if err != nil {
log.Fatal(err)
}
defer finder.Close()
if len(os.Args) > 2 {
command := os.Args[2]
switch command {
case "find":
if len(os.Args) > 3 {
pattern := os.Args[3]
results := finder.FindByName(pattern)
fmt.Printf("找到 %d 个匹配符号:\n", len(results))
for _, sym := range results {
fmt.Printf(" %-40s 类型:%v 值:0x%x 大小:%d\n",
sym.Name, sym.Type, sym.Value, sym.Size)
}
}
case "functions":
funcs := finder.ListFunctions()
fmt.Printf("函数数量:%d\n\n", len(funcs))
for _, fn := range funcs {
fmt.Printf(" %-40s 0x%x (%d 字节)\n",
fn.Name, fn.Value, fn.Size)
}
case "variables":
vars := finder.ListVariables()
fmt.Printf("变量数量:%d\n\n", len(vars))
for _, v := range vars {
fmt.Printf(" %-40s 0x%x (%d 字节)\n",
v.Name, v.Value, v.Size)
}
case "main":
main := finder.FindMain()
if main != nil {
fmt.Printf("找到 main 函数:\n")
fmt.Printf(" 名称:%s\n", main.Name)
fmt.Printf(" 地址:0x%x\n", main.Value)
fmt.Printf(" 大小:%d 字节\n", main.Size)
} else {
fmt.Println("未找到 main 函数")
}
default:
// 显示所有符号
symbols, _ := finder.file.Symbols()
fmt.Printf("所有符号 (%d 个):\n\n", len(symbols))
for _, sym := range symbols {
fmt.Printf(" %-40s 类型:%v 值:0x%x 大小:%d\n",
sym.Name, sym.Type, sym.Value, sym.Size)
}
}
} else {
// 默认显示所有符号
symbols, _ := finder.file.Symbols()
fmt.Printf("所有符号 (%d 个):\n\n", len(symbols))
for _, sym := range symbols {
fmt.Printf(" %-40s 类型:%v 值:0x%x 大小:%d\n",
sym.Name, sym.Type, sym.Value, sym.Size)
}
}
}
示例 7:比较两个对象文件
package main
import (
"debug/plan9obj"
"fmt"
"log"
"sort"
)
// compareFiles 比较两个对象文件
func compareFiles(file1, file2 *plan9obj.File) {
fmt.Println("=== 文件比较 ===\n")
// 1. 比较文件头
fmt.Println("文件头比较:")
fmt.Printf(" 文件 1 魔术数字:0x%x\n", file1.Magic)
fmt.Printf(" 文件 2 魔术数字:0x%x\n", file2.Magic)
fmt.Printf(" 文件 1 BSS: %d 字节\n", file1.Bss)
fmt.Printf(" 文件 2 BSS: %d 字节\n", file2.Bss)
fmt.Printf(" 文件 1 入口点:0x%x\n", file1.Entry)
fmt.Printf(" 文件 2 入口点:0x%x\n", file2.Entry)
fmt.Println()
// 2. 比较节
fmt.Println("节比较:")
sections1 := make(map[string]*plan9obj.Section)
sections2 := make(map[string]*plan9obj.Section)
for _, s := range file1.Sections {
sections1[s.Name] = s
}
for _, s := range file2.Sections {
sections2[s.Name] = s
}
// 查找共同的节
commonNames := make([]string, 0)
for name := range sections1 {
if _, ok := sections2[name]; ok {
commonNames = append(commonNames, name)
}
}
sort.Strings(commonNames)
fmt.Printf("共同节:%d 个\n", len(commonNames))
for _, name := range commonNames {
s1 := sections1[name]
s2 := sections2[name]
diff := ""
if s1.Size != s2.Size {
diff = fmt.Sprintf(" (大小差异:%d vs %d)", s1.Size, s2.Size)
}
fmt.Printf(" %s: %d -> %d 字节%s\n", name, s1.Size, s2.Size, diff)
}
// 查找新增的节
fmt.Println("\n新增的节:")
for name := range sections2 {
if _, ok := sections1[name]; !ok {
fmt.Printf(" + %s (%d 字节)\n", name, sections2[name].Size)
}
}
// 查找删除的节
fmt.Println("\n删除的节:")
for name := range sections1 {
if _, ok := sections2[name]; !ok {
fmt.Printf(" - %s (%d 字节)\n", name, sections1[name].Size)
}
}
// 3. 比较符号
fmt.Println("\n符号比较:")
syms1, _ := file1.Symbols()
syms2, _ := file2.Symbols()
symMap1 := make(map[string]plan9obj.Symbol)
symMap2 := make(map[string]plan9obj.Symbol)
for _, sym := range syms1 {
symMap1[sym.Name] = sym
}
for _, sym := range syms2 {
symMap2[sym.Name] = sym
}
fmt.Printf("文件 1 符号数:%d\n", len(symMap1))
fmt.Printf("文件 2 符号数:%d\n", len(symMap2))
// 新增的符号
added := make([]string, 0)
for name := range symMap2 {
if _, ok := symMap1[name]; !ok {
added = append(added, name)
}
}
sort.Strings(added)
fmt.Printf("\n新增符号:%d 个\n", len(added))
for i, name := range added {
if i >= 20 {
break
}
fmt.Printf(" + %s\n", name)
}
if len(added) > 20 {
fmt.Printf(" ... 还有 %d 个\n", len(added)-20)
}
// 删除的符号
removed := make([]string, 0)
for name := range symMap1 {
if _, ok := symMap2[name]; !ok {
removed = append(removed, name)
}
}
sort.Strings(removed)
fmt.Printf("\n删除符号:%d 个\n", len(removed))
for i, name := range removed {
if i >= 20 {
break
}
fmt.Printf(" - %s\n", name)
}
if len(removed) > 20 {
fmt.Printf(" ... 还有 %d 个\n", len(removed)-20)
}
// 变化的符号
changed := make([]string, 0)
for name, sym1 := range symMap1 {
sym2, ok := symMap2[name]
if ok {
if sym1.Value != sym2.Value || sym1.Size != sym2.Size {
changed = append(changed, name)
}
}
}
sort.Strings(changed)
fmt.Printf("\n变化的符号:%d 个\n", len(changed))
for i, name := range changed {
if i >= 20 {
break
}
sym1 := symMap1[name]
sym2 := symMap2[name]
fmt.Printf(" ~ %s (0x%x/%d -> 0x%x/%d)\n",
name, sym1.Value, sym1.Size, sym2.Value, sym2.Size)
}
if len(changed) > 20 {
fmt.Printf(" ... 还有 %d 个\n", len(changed)-20)
}
}
func main() {
if len(os.Args) < 3 {
log.Fatal("用法:compare <file1.9> <file2.9>")
}
file1, err := plan9obj.Open(os.Args[1])
if err != nil {
log.Fatal("打开文件 1 失败:", err)
}
defer file1.Close()
file2, err := plan9obj.Open(os.Args[2])
if err != nil {
log.Fatal("打开文件 2 失败:", err)
}
defer file2.Close()
compareFiles(file1, file2)
}
安全最佳实践
✅ 推荐做法
-
始终检查错误
f, err := plan9obj.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("数据太小") }
❌ 不安全做法
-
不要忽略错误
// ❌ 错误 f, _ := plan9obj.Open("file") // ✅ 正确 f, err := plan9obj.Open("file") if err != nil { // 处理错误 } -
不要忘记关闭文件
// ❌ 错误 f, _ := plan9obj.Open("file") // ✅ 正确 f, _ := plan9obj.Open("file") defer f.Close() -
不要假设节一定存在
// ❌ 错误 data, _ := f.Section(".text").Data() // ✅ 正确 section := f.Section(".text") if section == nil { return error } data, err := section.Data()
总结
核心类型
File // Plan 9 对象文件
FileHeader // 文件头
Section // 节
SectionHeader // 节头
Symbol // 符号
Reloc // 重定位
使用场景
| 场景 | 推荐方法 | 说明 |
|---|---|---|
| 打开文件 | plan9obj.Open() | 读取 Plan 9 对象文件 |
| 读取节 | File.Section() | 获取特定节 |
| 读取符号 | File.Symbols() | 获取符号表 |
| 获取字符串 | File.StringTable() | 获取字符串表 |
魔术数字
| 格式 | 常量 | 说明 |
|---|---|---|
| 32 位 | Magic32 | 32 位对象文件 |
| 64 位 | Magic64 | 64 位对象文件 |
节类型
| 类型 | 常量 | 说明 |
|---|---|---|
| 无效 | TypeNull | 无效节 |
| 代码 | TypeText | 代码段 |
| 数据 | TypeData | 数据段 |
| BSS | TypeBSS | BSS 段 |
| 字符串 | TypeString | 字符串表 |
| 符号 | TypeSymbol | 符号表 |
符号类型
| 类型 | 常量 | 说明 |
|---|---|---|
| 无类型 | SymTypeNone | 无类型符号 |
| 代码 | SymTypeText | 函数/代码 |
| 数据 | SymTypeData | 数据对象 |
| BSS | SymTypeBSS | 未初始化数据 |
| 公共 | SymTypeCommon | 公共符号 |
常见节
| 节名 | 用途 |
|---|---|
.text | 代码节 |
.data | 数据节 |
.bss | BSS 节 |
.symtab | 符号表 |
.strtab | 字符串表 |
.rodata | 只读数据节 |
与其他格式比较
| 特性 | Plan 9 | ELF | Mach-O | PE |
|---|---|---|---|---|
| 复杂度 | 简单 | 复杂 | 中等 | 复杂 |
| 平台 | Plan 9 | Unix | macOS | Windows |
| 用途 | 历史/教学 | 通用 | Apple | Windows |
| 大小 | 小 | 大 | 中 | 大 |
参考资料
- Go debug/plan9obj 包文档
- Plan 9 操作系统文档
- Plan 9 对象文件格式
- Go 工具链文档
- debug/elf 包文档
- debug/macho 包文档
- debug/pe 包文档
最后更新:2026-04-03
Go 版本:Go 1.23+