Linux命令-nm(列出目标文件(可执行文件、对象文件、库文件)中的符号)

nm 是 Linux 中用于列出目标文件(可执行文件、对象文件、库文件)中的符号的命令行工具。它是二进制分析和调试的重要工具,可以显示函数、变量等符号的名称、类型和地址信息。

📦 基本语法

bash 复制代码
nm [选项] [文件...]

默认行为 :如果不指定文件,nm 会尝试读取 a.out 文件。

🎯 常用选项详解

选项 说明
符号类型过滤
-a, --debug-syms 显示所有符号,包括调试符号
-g, --extern-only 只显示外部(全局)符号
-u, --undefined-only 只显示未定义的符号
--defined-only 只显示已定义的符号
符号排序
-n, --numeric-sort 按符号地址排序(数字顺序)
-r, --reverse-sort 反序排序
-p, --no-sort 不排序,按符号表顺序显示
-v, --numeric-sort 按符号地址排序(同 -n
输出格式
-f, --format=格式 指定输出格式(bsd, sysv, posix)
-t, --radix=基数 设置地址基数(d=十进制, o=八进制, x=十六进制)
-l, --line-numbers 显示符号的行号信息(需要调试信息)
-S, --print-size 显示符号的大小
-s, --print-armap 显示归档文件的索引
-A, --print-file-name 在每个符号前显示文件名
-o, --print-file-name -A(BSD 格式)
符号名称处理
-C, --demangle 解码 C++ 符号(demangle)为可读格式
--no-demangle 不解码 C++ 符号
--special-syms 显示特殊符号
其他选项
-D, --dynamic 显示动态符号,而不是普通符号
--with-symbol-versions 显示符号版本信息
--size-sort 按符号大小排序
--help 显示帮助信息
--version 显示版本信息

🔍 符号类型说明

nm 输出的符号类型字符(第二列):

字符 类型 说明
A 绝对地址符号 值不会因重定位而改变
B 未初始化数据段(BSS) 位于 .bss
C 公共符号 未初始化的数据
D 已初始化数据段 位于 .data
G 已初始化数据段(小对象) 位于 .sdata 节(某些架构)
I 间接引用另一个符号
N 调试符号
R 只读数据段 位于 .rodata
S 未初始化数据段(小对象) 位于 .sbss 节(某些架构)
T 文本(代码)段 位于 .text
U 未定义符号 在当前文件中未定义
V 弱符号 弱定义符号
W 弱符号(未标记) 弱引用符号
- 调试符号(stabs)
? 未知类型

小写字母 表示局部符号,大写字母表示全局(外部)符号。

💡 核心用法示例

1. 基本符号列表
bash 复制代码
# 列出目标文件中的所有符号
nm myprogram.o

# 列出可执行文件中的符号
nm /bin/ls

# 列出动态库中的符号
nm /lib/x86_64-linux-gnu/libc.so.6

# 列出静态库中的符号
nm libmylib.a

# 列出多个文件的符号
nm *.o
2. 按类型过滤符号
bash 复制代码
# 只显示未定义的符号(需要其他文件提供)
nm -u myprogram.o

# 只显示已定义的符号
nm --defined-only myprogram.o

# 只显示外部(全局)符号
nm -g myprogram.o

# 显示所有符号,包括调试符号
nm -a myprogram.o

# 显示动态符号(用于共享库)
nm -D libmylib.so
3. 排序和格式控制
bash 复制代码
# 按地址排序(数字顺序)
nm -n myprogram.o

# 反序排序(地址从高到低)
nm -r myprogram.o

# 不排序,按符号表顺序显示
nm -p myprogram.o

# 显示符号大小
nm -S myprogram.o

# 显示每个符号的文件名(用于多个文件)
nm -A *.o

# 以十六进制显示地址和大小
nm -t x myprogram.o

# 以十进制显示地址
nm -t d myprogram.o
4. 符号名称处理
bash 复制代码
# 解码 C++ 符号(demangle)
nm -C myprogram.cpp.o

# 不解码 C++ 符号
nm --no-demangle myprogram.cpp.o

# 显示符号的行号信息(需要编译时加 -g)
nm -l myprogram.o
5. 输出格式控制
bash 复制代码
# BSD 格式(默认)
nm -f bsd myprogram.o

# System V 格式
nm -f sysv myprogram.o

# POSIX 格式
nm -f posix myprogram.o

# 自定义格式:地址、类型、大小、名称
nm -t d -S -f posix myprogram.o

📊 输出示例解读

示例 1:简单目标文件
bash 复制代码
# 编译示例程序
echo 'int global_var = 42; static int static_var = 100; void func() {} int main() { return 0; }' > test.c
gcc -c test.c -o test.o

# 查看符号
nm test.o

输出:

复制代码
0000000000000000 T func
0000000000000000 D global_var
0000000000000000 T main
0000000000000004 d static_var

解读

  • func:全局文本(代码)符号(T),地址 0x0
  • global_var:全局已初始化数据(D),地址 0x0
  • main:全局文本符号(T),地址 0x0
  • static_var:局部已初始化数据(d),地址 0x4
示例 2:C++ 程序
bash 复制代码
# C++ 示例
echo 'class MyClass { public: void method(); }; void MyClass::method() {}' > test.cpp
g++ -c test.cpp -o test.o

# 未解码的符号
nm test.o
# 输出:_ZN7MyClass6methodEv

# 解码后的符号
nm -C test.o
# 输出:MyClass::method()

🔧 实用场景示例

场景 1:查找未定义符号(解决链接错误)
bash 复制代码
# 查找对象文件中未定义的符号(需要链接的符号)
nm -u myprogram.o

# 查找共享库中未定义的符号
nm -Du libmylib.so

# 查找可执行文件中未定义的符号(通常很少)
nm -u /bin/ls

# 查找特定未定义符号
nm -u myprogram.o | grep "printf"
场景 2:分析库文件
bash 复制代码
# 查看静态库中的符号
nm libmylib.a

# 查看动态库中的符号
nm -D libmylib.so

# 查看库中定义的函数
nm -g libmylib.a | grep " T "

# 查看库中的数据符号
nm -g libmylib.a | grep " D "

# 查看库的大小(通过符号大小估算)
nm -S libmylib.a | awk '{sum += strtonum("0x"$2)} END {print sum}'
场景 3:调试符号信息
bash 复制代码
# 显示带行号信息的符号(需要编译时加 -g)
gcc -g -c test.c -o test.o
nm -l test.o

# 显示所有符号,包括调试符号
nm -a test.o

# 查找特定函数的符号信息
nm -C test.o | grep "my_function"

# 查看 C++ 类的成员函数
nm -C test.o | grep "MyClass::"
场景 4:比较两个版本
bash 复制代码
# 比较两个对象文件的符号差异
diff <(nm -n old.o) <(nm -n new.o)

# 比较两个库文件的导出符号
diff <(nm -g libold.a) <(nm -g libnew.a)

# 检查新版本是否缺少某些符号
comm -13 <(nm -g old.o | awk '{print $3}' | sort) \
         <(nm -g new.o | awk '{print $3}' | sort)

⚙️ 实用脚本示例

脚本 1:符号检查工具
bash 复制代码
#!/bin/bash
# 符号检查工具

FILE="$1"

if [ -z "$FILE" ] || [ ! -f "$FILE" ]; then
    echo "用法: $0 <二进制文件>"
    exit 1
fi

echo "=== 符号检查: $FILE ==="
echo ""

# 1. 基本符号统计
echo "1. 符号类型统计:"
nm "$FILE" | awk '
    {
        type = substr($2, 1, 1)
        count[type]++
        total++
    }
    END {
        for (t in count) {
            printf("  %s: %d (%.1f%%)\n", t, count[t], count[t]/total*100)
        }
        printf("  总计: %d\n", total)
    }
'

# 2. 未定义符号列表
echo -e "\n2. 未定义符号:"
UNDEFINED=$(nm -u "$FILE" | wc -l)
if [ "$UNDEFINED" -gt 0 ]; then
    nm -u "$FILE" | head -20
    if [ "$UNDEFINED" -gt 20 ]; then
        echo "  ... 还有 $((UNDEFINED - 20)) 个未显示"
    fi
else
    echo "  无"
fi

# 3. 全局符号列表
echo -e "\n3. 全局符号(前20个):"
nm -g "$FILE" | head -20

# 4. 按大小排序的符号
echo -e "\n4. 最大的符号(前10个):"
nm -S --size-sort "$FILE" 2>/dev/null | tail -10

# 5. 动态符号(如果是共享库或可执行文件)
if file "$FILE" | grep -q "dynamically linked"; then
    echo -e "\n5. 动态符号数量:"
    nm -D "$FILE" | wc -l
fi
脚本 2:库文件分析器
bash 复制代码
#!/bin/bash
# 库文件分析器

LIB_FILE="$1"

if [ -z "$LIB_FILE" ] || [ ! -f "$LIB_FILE" ]; then
    echo "用法: $0 <库文件>"
    echo "支持: .a (静态库), .so (动态库)"
    exit 1
fi

echo "=== 库文件分析: $LIB_FILE ==="
echo "文件类型: $(file "$LIB_FILE")"
echo ""

if [[ "$LIB_FILE" == *.a ]]; then
    # 静态库分析
    echo "1. 静态库内容:"
    ar t "$LIB_FILE"
    
    echo -e "\n2. 符号表摘要:"
    nm -g "$LIB_FILE" | awk '
        {
            type = substr($2, 1, 1)
            if (type == "T") { code++ }
            else if (type == "D" || type == "B" || type == "R") { data++ }
            else if (type == "U") { undef++ }
            else { other++ }
            total++
        }
        END {
            printf("  代码符号: %d\n", code)
            printf("  数据符号: %d\n", data)
            printf("  未定义符号: %d\n", undef)
            printf("  其他符号: %d\n", other)
            printf("  总计: %d\n", total)
        }
    '
    
    echo -e "\n3. 按对象文件统计:"
    for obj in $(ar t "$LIB_FILE"); do
        if [[ "$obj" == *.o ]]; then
            COUNT=$(nm -g "$LIB_FILE"("$obj") 2>/dev/null | wc -l)
            echo "  $obj: $COUNT 个符号"
        fi
    done
    
elif [[ "$LIB_FILE" == *.so ]]; then
    # 动态库分析
    echo "1. 动态符号表:"
    nm -D "$LIB_FILE" | head -20
    
    echo -e "\n2. 版本信息:"
    objdump -p "$LIB_FILE" | grep -A5 "Version References:"
    
    echo -e "\n3. 依赖库:"
    ldd "$LIB_FILE" 2>/dev/null || echo "  无法获取依赖"
    
else
    echo "错误: 不支持的文件类型"
    exit 1
fi
脚本 3:符号查找工具
bash 复制代码
#!/bin/bash
# 符号查找工具

PATTERN="$1"
DIR="${2:-.}"
EXTENSIONS="${3:-o,a,so}"

if [ -z "$PATTERN" ]; then
    echo "用法: $0 <符号模式> [目录] [文件扩展名]"
    echo "示例: $0 my_function . o,so,a"
    exit 1
fi

echo "搜索符号: $PATTERN"
echo "目录: $DIR"
echo "文件扩展名: $EXTENSIONS"
echo ""

IFS=',' read -ra EXT_ARRAY <<< "$EXTENSIONS"

for ext in "${EXT_ARRAY[@]}"; do
    echo "=== 搜索 .$ext 文件 ==="
    
    find "$DIR" -name "*.$ext" -type f | while read -r file; do
        # 使用 nm 查找符号
        if nm "$file" 2>/dev/null | grep -q "$PATTERN"; then
            echo "文件: $file"
            nm "$file" 2>/dev/null | grep "$PATTERN" | while read -r line; do
                echo "  $line"
            done
            echo ""
        fi
    done
done

🔄 相关命令对比

命令 用途 特点
nm 列出目标文件符号 专业符号分析,支持多种格式
objdump -t 显示符号表 更详细的符号信息
readelf -s 显示 ELF 文件符号表 ELF 专用,信息最全
strings 提取文件中的字符串 查找可打印字符串
ldd 显示共享库依赖 动态库依赖关系
file 确定文件类型 快速查看文件信息

⚠️ 重要注意事项

  1. 文件类型nm 只能用于目标文件(.o)、静态库(.a)、动态库(.so)和可执行文件。
  2. 调试信息 :需要编译时加 -g 选项才能显示行号信息。
  3. C++ 符号 :使用 -C 选项解码 C++ 符号,否则显示混淆后的名称。
  4. 动态符号 :使用 -D 查看动态符号表(.dynsym 节),而不是常规符号表(.symtab 节)。
  5. 跨平台 :不同架构的目标文件可能需要对应的 nm 版本。

🎯 最佳实践

  1. 调试链接错误 :使用 nm -u 查找未定义符号。
  2. 库文件分析 :使用 nm -g 查看库提供的接口。
  3. 版本比较:比较不同版本二进制文件的符号变化。
  4. 安全审计:检查二进制文件中的可疑符号。
  5. 性能分析:通过符号大小分析代码膨胀问题。

nm 是二进制分析和调试的重要工具,特别在解决链接问题、理解库接口和逆向工程中非常有用。掌握 nm 命令可以帮助你更好地理解程序的内部结构。

相关推荐
c++逐梦人2 小时前
⽹络基础概念
linux·网络
杨云龙UP2 小时前
Oracle / ODA环境TRACE、alert日志定位与ADRCI清理 SOP_20260423
linux·运维·服务器·数据库·oracle
@土豆2 小时前
Jenkins CI_CD流水线案例
运维·ci/cd·jenkins
深色風信子2 小时前
Docker sub2api
运维·docker·容器·sub2api
REDcker2 小时前
跨平台编译详解 工具链配置与工程化实践
linux·c++·windows·macos·c·跨平台·编译
Sapphire~2 小时前
Linux-15 ubuntu 和 windows 双系统,更新系统导致丢失ubuntu 入口
linux·运维·ubuntu
zzzsde2 小时前
【Linux】线程概念与控制(1)线程基础与分页式存储管理
linux·运维·服务器·开发语言·算法
小樱花的樱花2 小时前
Linux进程管理相关命令
linux·运维·服务器
计算机安禾2 小时前
【Linux从入门到精通】第13篇:磁盘管理与文件系统——数据存在哪了?
linux·运维·服务器