当前位置:首页 > 文章列表 > Golang > Go教程 > Go plan9obj.Symbols 怎样读取 Plan 9 文件的符号表

Go plan9obj.Symbols 怎样读取 Plan 9 文件的符号表

来源:17golang原创 2026-10-27 20:22:02 0浏览 收藏

如果手里有一个需要分析的 Plan 9 a.out 文件,Go 标准库的 debug/plan9obj 可以直接读取它的符号表。核心调用是 File.Symbols():成功时得到 []plan9obj.Sym,每个符号包含 Value、Type 和 Name;文件没有符号段时则返回 plan9obj.ErrNoSymbols。

官方文档:https://pkg.go.dev/debug/plan9obj#File.Symbols

本文只讨论读取自有的 Plan 9 a.out 文件,不涉及修改目标文件、绕过权限或处理不受信任的输入。文中的配图是静态说明图,不是运行截图。

Plan 9 a.out 文件经过 plan9obj 打开后由 Symbols 返回 []Sym 的结构示意图
图1:从 Plan 9 a.out 文件到 []Sym 的调用关系说明图。

先看懂 Symbols 返回什么

plan9obj.File 表示一个已打开的 Plan 9 a.out 文件。调用 Symbols() 后,返回值不是一个需要再次打开的 section,而是一个符号切片:

type Sym struct {
	Value uint64 // 符号对应的数值或地址字段
	Type  rune   // 符号类型标记
	Name  string // 符号名称
}

因此,读取逻辑通常是“打开文件—读取符号切片—遍历字段”。不要把 Symbols() 和 Section(".text") 混用:前者面向符号表,后者面向指定 section。

用 Open 和 Symbols 读取符号表

最小可用程序可以把目标文件路径作为命令行参数。plan9obj.Open 负责打开并解析 Plan 9 a.out 文件,读取完成后用 Close 释放由打开动作取得的资源。

package main

import (
	"fmt"     // 输出读取到的符号信息
	"os"      // 读取命令行参数

	"debug/plan9obj" // 解析 Plan 9 a.out 文件
)

func main() {
	if len(os.Args) != 2 {
		fmt.Fprintln(os.Stderr, "用法:read-symbols FILE") // 明确要求一个目标文件
		os.Exit(2)
	}

	f, err := plan9obj.Open(os.Args[1])
	if err != nil {
		fmt.Fprintf(os.Stderr, "打开 Plan 9 文件失败:%v\n", err) // 打开或基础解析失败在这里返回
		os.Exit(1)
	}
	defer f.Close() // Open 成功后确保文件资源最终关闭

	symbols, err := f.Symbols()
	if err != nil {
		fmt.Fprintf(os.Stderr, "读取符号表失败:%v\n", err) // 缺少符号段也会在这里被识别
		os.Exit(1)
	}

	for _, sym := range symbols {
		fmt.Printf("0x%x %c %s\n", sym.Value, sym.Type, sym.Name) // 按字段输出每个符号
	}
}

这个程序的边界很清楚:参数数量不对时不碰文件;打开失败时不调用 Symbols;打开成功后用 defer f.Close() 管理资源;只有符号表读取成功,才遍历返回切片。

怎样处理 Value、Type 和 Name

Value 是 uint64,打印地址或数值时可以使用十六进制;Type 是 rune,适合使用 %c 展示单个类型标记,也可以按数值进一步分类;Name 是普通字符串,可能为空,因此不要把空名称直接当成读取失败。

func printSymbols(symbols []plan9obj.Sym) {
	for index, sym := range symbols {
		name := sym.Name
		if name == "" {
			name = "" // 空名称只代表字段为空,不把它当成错误
		}
		fmt.Printf("[%d] value=0x%x type=%q name=%s\n", index, sym.Value, sym.Type, name) // 保留字段类型信息
	}
}

如果后续需要按名称查找,可以在遍历时建立 map[string]plan9obj.Sym;如果需要保留重复名称,则应继续使用原始切片。这里不额外推断符号是否可执行,判断只依据返回的字段。

ErrNoSymbols 和普通读取错误要分开

官方文档明确说明:当文件没有符号段时,File.Symbols 返回 ErrNoSymbols。这是一种可识别的输入状态,不应和文件打不开、格式解析失败或底层读取失败混为一谈。

Symbols 有符号段返回 []Sym、无符号段返回 ErrNoSymbols 的分支关系示意图
图2:区分有符号段、无符号段与其它错误的结果关系说明图。
func readSymbols(path string) ([]plan9obj.Sym, error) {
	f, err := plan9obj.Open(path)
	if err != nil {
		return nil, fmt.Errorf("打开 Plan 9 文件:%w", err) // 这类错误发生在 Symbols 之前
	}
	defer f.Close() // 无论后续返回哪种结果都关闭资源

	symbols, err := f.Symbols()
	if err != nil {
		if errors.Is(err, plan9obj.ErrNoSymbols) {
			return nil, fmt.Errorf("文件没有符号段:%w", err) // 给调用方保留可判断的原因
		}
		return nil, fmt.Errorf("读取符号表:%w", err) // 其它读取错误继续向上返回
	}
	return symbols, nil
}

调用方可以再次使用 errors.Is 判断是否为 plan9obj.ErrNoSymbols。如果要把“没有符号表”当作可接受的扫描结果,可以在这一层转成空切片;如果它意味着输入不符合业务要求,则应继续返回错误。

输入边界与资源处理

plan9obj.NewFile 还可以从实现 io.ReaderAt 的对象创建文件解析器,且底层 Plan 9 二进制应从 reader 的位置 0 开始。对于普通路径读取,Open 更直接;对于已经由程序管理的随机访问数据,才考虑 NewFile。

func readFromReaderAt(r io.ReaderAt) ([]plan9obj.Sym, error) {
	f, err := plan9obj.NewFile(r)
	if err != nil {
		return nil, fmt.Errorf("解析 ReaderAt 中的 Plan 9 文件:%w", err) // reader 必须从文件起点提供数据
	}
	// NewFile 没有接管外部 reader 的关闭责任,因此这里不调用 f.Close 来关闭外部对象。
	return f.Symbols()
}

还要注意官方文档对该包的安全提示:它只做基础校验,不适合直接把不受信任的异常输入当成安全数据处理。本文示例的“自有文件”边界,意味着调用方应先确定输入来源,再决定是否进入解析流程。

迁移清单:把读取逻辑固定下来

环节推荐做法结果判断
打开使用 plan9obj.Open 并检查错误成功后才拥有可调用 Symbols 的 File
释放Open 成功后立即 defer f.Close()路径读取不会遗留打开资源
读取调用 f.Symbols()成功得到 []plan9obj.Sym
分支用 errors.Is(err, plan9obj.ErrNoSymbols)识别“没有符号段”这一明确状态
输出按 Value、Type、Name 处理不混淆字段类型和空名称

常见问题

Symbols 返回空切片就一定有错误吗?

不能只凭切片长度下结论。首先检查 error;没有符号段时应识别 ErrNoSymbols,只有 err == nil 时才把返回切片当作成功结果继续处理。

为什么不直接读取某个 section?

section 和符号表是不同层次的数据。标题任务是读取符号表时,优先调用 Symbols,避免自行按文件偏移解析符号记录。

NewFile 创建的 File 需要关闭吗?

官方文档说明,直接由 NewFile 创建的 File 调用 Close 不会产生效果;外部 reader 的生命周期仍由创建它的代码管理。

可以用 plan9obj 解析不受信任的文件吗?

应谨慎。官方文档提示该包只做基础校验,异常输入可能消耗较多资源或触发 panic。本文示例限定为自有文件,不把它当作通用安全沙箱。

归纳起来,Go 读取 Plan 9 文件符号表的稳定写法就是:先用 Open 建立 File,再调用 Symbols,按 Sym 字段消费结果,并用 ErrNoSymbols 单独处理“没有符号段”。

版本声明
本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
Go html/template Delims 怎样切换模板动作分隔符Go html/template Delims 怎样切换模板动作分隔符
上一篇
Go html/template Delims 怎样切换模板动作分隔符
Go expvar 暴露运行时指标的接口设计
下一篇
Go expvar 暴露运行时指标的接口设计
查看更多
最新文章
查看更多
课程推荐
  • 前端进阶之JavaScript设计模式
    前端进阶之JavaScript设计模式
    设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
    543次学习
  • GO语言核心编程课程
    GO语言核心编程课程
    本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
    516次学习
  • 简单聊聊mysql8与网络通信
    简单聊聊mysql8与网络通信
    如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
    500次学习
  • JavaScript正则表达式基础与实战
    JavaScript正则表达式基础与实战
    在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
    487次学习
  • 从零制作响应式网站—Grid布局
    从零制作响应式网站—Grid布局
    本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
    485次学习
查看更多
AI推荐
  • PubMedQA数据集详解:生物医学问答基准、功能与应用指南
    PubMedQA
    深入了解PubMedQA生物医学问答数据集,涵盖其核心功能、使用方法及在临床决策、药物研发等场景的应用,助力提升NLP模型性能。
    741次使用
  • H2O EvalGPT:开源LLM大模型评估与排行榜工具
    H2O EvalGPT
    H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
    738次使用
  • LMArena是什么?伯克利AI模型评估平台使用指南与功能解析
    LMArena
    LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
    822次使用
  • 斯坦福HELM:大语言模型Holistic Evaluation整体评估框架详解
    HELM
    深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
    786次使用
  • MMBench详解:多模态大模型基准测试、功能特点与使用指南
    MMBench
    MMBench是由上海人工智能实验室等机构联合推出的多模态基准测试平台,提供细粒度能力评估、大规模数据集及VLMEvalKit工具。本文详细介绍其核心功能、安装使用方法及应用场景,助力开发者全面评估多模态模型性能。
    617次使用
微信登录更方便
  • 密码登录
  • 注册账号
登录即同意 用户协议 和 隐私政策
返回登录
  • 重置密码