1. 写在前面三个报错其实是一条路上的三个坑如果你是刚接触嵌入式或者操作系统课程大概率会在Ubuntu 20.04上尝试编译一段ARM汇编。我的实际情况就是这样按照某本教材敲了个hello.s然后依次执行arm-linux-objdump -d hello.s和bash hello.s结果屏幕上蹦出来两条刺眼的报错——arm-linux-objdump: command not found和bash: hello.s: Permission denied。先说结论这两个报错不是同一个原因但都是同一个根源——工具链没配好文件没有一个正确的处理姿势。arm-linux-objdump属于交叉编译工具链不是系统自带的原生objdump需要单独安装而bash hello.s报权限拒绝是因为把汇编源码当成了可执行脚本来跑或者当前目录下的文件没有执行权限。中间还夹了一个“手动安装binutils”的操作很多人会绕远路去源码编译其实完全没必要。这篇文章就围绕这三个问题展开把背后的原理、排查思路和完整解决步骤全部梳理清楚。适合的人群很明确正在Ubuntu 20.04上做ARM汇编实验的学生、刚入门嵌入式交叉编译的开发者以及任何被command not found折磨过的人。我会把每一步怎么查、怎么做、为什么要这样做都写明白。2. 先摸清三个报错的真实含义动手解决之前必须先把报错本身吃透。很多人一看见command not found就去百度“怎么安装objdump”结果装了半天还是不行就是因为没搞清楚objdump和arm-linux-objdump的区别。2.1 objdump到底是什么系统里默认有吗objdump是GNU binutils工具集里的一个重要成员作用是把编译好的目标文件、可执行文件进行反汇编、查看节区信息、导出头部数据等。简单说它是一个“文件解剖师”——你想知道一个二进制文件内部长什么样用它就对了。在x86架构的Ubuntu 20.04上如果你只是敲objdump大概率是能用的因为系统默认安装了binutils。但注意这个objdump是x86架构的它只能解析x86的目标文件。你手头编译出来的hello.o是ARM指令集用x86的objdump去解析要么报错要么输出一堆无法识别的数据。这就是第一个关键点arm-linux-objdump不是“objdump的ARM版本”而是“运行在x86上、但目标是ARM的交叉工具”。它的前缀arm-linux-表示目标平台是ARM架构、Linux系统。这一类工具还有arm-linux-gcc、arm-linux-as、arm-linux-ld全都在交叉编译工具链里。2.2 “command not found”的常见误判当我执行arm-linux-objdump -d hello.s得到command not found时第一反应是“系统里没有objdump我需要装一个”。当时我还真去手动下载了binutils源码准备自己编译——这就是标题里“手动安装binutils”的由来。但后来发现这个动作大概率是多余的而且容易踩坑。command not found有三层可能命令真的没装系统PATH里找不到对应的可执行文件。命令装了但安装路径不在PATH环境变量里shell找不到。命令名称不对你以为叫arm-linux-objdump实际安装后的名称是arm-linux-gnueabihf-objdump或者其他变体。第三种情况在交叉编译工具链里极其常见。不同发行版、不同工具链版本命令前缀的命名规则不一样。Ubuntu的软件源里有一个叫做binutils-arm-linux-gnueabihf的包装完之后生成的命令是arm-linux-gnueabihf-objdump而不是arm-linux-objdump。所以你即使装了包敲原来的名字照样报command not found。注意交叉编译工具链的命令前缀不是统一的。arm-linux-、arm-linux-gnueabi-、arm-linux-gnueabihf-、aarch64-linux-gnu-都有可能出现区别在于ABI调用约定是软浮点还是硬浮点以及目标架构位数。看到前缀先别慌按我说的方式检查一下再动手。2.3 Permission denied的真相你在试图运行一个汇编源文件第二个报错bash: hello.s: Permission denied就更好玩了。如果你敲的是bash hello.s那你的本意是“用bash来解析并执行这个文件”。但hello.s是ARM汇编源代码里面是一堆mov、ldr、str之类的指令不是shell脚本。bash尝试把它当脚本解释第一行如果是#include或者.global之类的汇编伪指令bash根本不认识行为不可预期。再加上文件本身没执行权限bash会直接拒绝运行于是弹出Permission denied。或者你敲的是./hello.s那就更直白了——你想让内核加载这个文件作为程序执行。但如果文件不是合法的可执行格式没经过编译链接生成ELF文件并且没有执行权限内核会直接返回Permission denied或者Exec format error。这里必须纠正一个观念汇编源文件hello.s是一个文本文件不是一个可执行程序。你不能像运行脚本或程序那样直接“运行”它。正确的流程是hello.s --arm-linux-gnueabihf-as-- hello.o --arm-linux-gnueabihf-ld-- helloELF可执行文件也就是说必须先汇编、再链接最后得到的hello才是能在ARM环境或在QEMU模拟、或通过其他方式运行的程序。而objdump反汇编的对象通常是hello.o或者最终的hello不是.s源文件本身。虽然objdump也能看源文件里的文本但它的设计目标是处理二进制目标文件。2.4 一条完整的报错链背后的逻辑把三个报错连起来看其实就是一个典型的“新手在Linux上做ARM汇编实验”的标准流程写好了hello.s想反汇编看看指令长啥样 → 用arm-linux-objdump→ 命令不存在想直接运行 → 用bash hello.s→ 权限不足或格式错误根本问题不是某个包缺失而是“工具链没有完整搭建 对文件处理流程理解不到位”。这也解释了为什么网上杂七杂八的答案让你“安装binutils”“chmod x”“改用qemu”都能沾点边但都解决不了核心矛盾——因为每个人卡住的环节不一样。接下来我就按正确顺序把每一步的排查方法和解决方案完整写出来。3. 工具链排查与安装别急着源码编译先看系统里有什么这一节是整个解决过程的重头戏。我们按“先检查、再安装、最后验证”的顺序来。3.1 检查现状确认架构、确认已装包、确认PATH先打开终端执行下面几条命令把环境信息摸清楚。uname -m这行命令输出你当前系统的CPU架构。Ubuntu 20.04如果是常规安装大概率是x86_64。这意味着本机原生工具链都是x86体系ARM交叉工具链一个都不会有。which objdump which arm-linux-objdump which arm-linux-gnueabihf-objdumpwhich的作用是告诉你命令在哪。如果objdump有输出说明binutils已安装后面两个没有输出说明对应的交叉工具不存在。dpkg -l | grep binutilsdpkg -l列出所有已安装的软件包grep binutils过滤出和binutils相关的。这里会出现几种情况只有binutils和binutils-common说明你装的是x86版binutils没有交叉版。有binutils-arm-linux-gnueabihf说明交叉工具其实已经装了你只是命令名不对。什么都没有说明原生binutils都没装需要先把基础包补上。还有一个容易被忽略的检查项——PATH环境变量echo $PATH看看输出里有没有/usr/bin、/usr/local/bin这类常规路径。交叉工具链默认装完都在/usr/bin下只要这个目录在PATH里命令基本都能找到。如果你的工具链是手动下载解压到某个自定义目录的那就得手动把路径加进PATH否则就会出现“明明装好了却还是command not found”的诡异情况。3.2 安装方案选择为什么优先推荐apt而不是源码编译很多教程会让你去下载binutils源码包然后 configure、make、make install三连。我当年也这么干过但说句实在话——在Ubuntu 20.04上做ARM交叉调试用源码编译binutils纯属给自己找麻烦。原因有三点源码编译耗时长。binutils的configure和编译在普通机器上少说要好几分钟到十几分钟中间还可能因为缺少依赖如gmp、mpfr、libtextstyle等开发库而失败。版本过新未必兼容。源码里往往是最新版本但你的其他工具比如gcc、ld可能停留在系统版本版本不匹配会产生莫名其妙的链接错误。维护麻烦。源码编译的binutils不归apt管理以后卸载、升级都得手动处理很容易留下环境垃圾。而Ubuntu官方源里针对ARM交叉开发提供了一整套现成工具包安装只需要一条apt install版本与系统自带gcc匹配卸载升级都方便。除非你是在做一个需要定制binutils特性的特殊项目比如修改反汇编输出格式、给特定CPU架构打补丁否则完全用不到源码编译。实战心得我后来查了一下自己当年的笔记发现“手动安装binutils”的整个过程大概花了四十分钟而用apt安装只需要三十秒。四十分钟里大部分时间都是在跟make报错搏斗。如果你只是需要arm-linux-objdump看反汇编别再跳进源码编译的坑了。3.3 具体执行安装对应架构的binutils交叉工具包现在分情况给出命令。注意区分你的目标架构和ABI情况一目标平台是ARM 32位硬浮点绝大多数ARM开发板、树莓派默认sudo apt update sudo apt install binutils-arm-linux-gnueabihf装完以后可用的命令是arm-linux-gnueabihf-objdump、arm-linux-gnueabihf-as、arm-linux-gnueabihf-ld。验证arm-linux-gnueabihf-objdump --version输出类似GNU objdump (GNU Binutils for Ubuntu) ...就说明成功了。情况二目标平台是ARM 64位sudo apt install binutils-aarch64-linux-gnu命令前缀是aarch64-linux-gnu-。情况三你必须要arm-linux-objdump这个名字如果你手头的教材、脚本全是按照arm-linux-objdump这个名称写的不想改可以考虑装binutils-multiarchsudo apt install binutils-multiarch这个包提供arm-linux-objdump这类通用命名的多架构binutils。不过我实测下来这个包在Ubuntu 20.04上的可用性有时候不太稳定部分子命令可能缺失或版本偏老。更推荐的做法是装binutils-arm-linux-gnueabihf然后给命令做一个软链接。软链接方法很简单sudo ln -s /usr/bin/arm-linux-gnueabihf-objdump /usr/local/bin/arm-linux-objdump这样你再敲arm-linux-objdump实际上就是调用arm-linux-gnueabihf-objdump一劳永逸而且不污染系统包管理。同理需要arm-linux-as就再链接一个。3.4 装完还提示 command not found按这个顺序排查装完包之后仍然报command not found我整理了一个排查顺序建议严格按步骤来先确认包确实装上了dpkg -l | grep binutils-arm看状态是不是iiinstalled。如果显示un或没有结果说明安装失败回看有没有apt报错。确认命令实际名字ls /usr/bin/ | grep objdump看看系统里到底有什么前缀的objdump。这一步能直接终结“名字不对”的猜测。确认命令所在路径是否在PATH里如果你命令在/usr/bin/下正常没问题如果在自定义目录就用export PATH$PATH:/你的目录临时加进去或者写进~/.bashrc。检查是不是bash的hash缓存有时候bash会缓存命令路径导致新装的命令找不到。执行hash -r清除缓存再试。终极测试直接输命令的完整路径比如/usr/bin/arm-linux-gnueabihf-objdump -d hello.o。能跑通说明就是PATH或名称问题跑不通说明二进制本身有问题可能要检查架构兼容性。这套排查顺序我后来在很多机器上用过基本能在五分钟内定位问题。4. 汇编与链接实操从 .s 到可执行文件的完整流程工具链装好了接下来就是解决hello.s的正确处理方式。很多人卡在这一步是因为搞不清.s、.o、可执行文件三者的关系。我用最直白的方式讲一遍。4.1 源文件、目标文件、可执行文件三者到底是什么关系拿做饭来类比hello.s是菜谱上面写的是原料和步骤ARM指令。hello.o是备好的菜料已经切好、配好但还没下锅汇编器把指令翻译成机器码但还没分配好内存地址。hello可执行文件是出锅的菜可以直接吃链接器把目标文件组合起来填充地址生成可运行的ELF格式文件。Android里你没学过也没关系只要记住.s文件是给人看的文本.o和可执行文件是给机器看的二进制。objdump主要分析的是后两者。4.2 一个可直接照抄的完整示例假设你的hello.s是正统的ARM汇编比如在树莓派或QEMU里跑的那种内容是经典的“Hello, World”。现在执行以下三步# 第一步汇编 arm-linux-gnueabihf-as -o hello.o hello.s # 第二步链接 arm-linux-gnueabihf-ld -o hello hello.o # 第三步反汇编验证 arm-linux-gnueabihf-objdump -d hello如果一切顺利objdump会输出每个指令的地址、机器码和反汇编文本类似这样00010054 _start: 10054: e1a00000 mov r0, #0 10058: e59f0014 ldr r0, [pc, #20] ...看到这里恭喜你工具链已经完全跑通了。这里有几个细节值得注意as是汇编器assembler对应binutils包里的arm-linux-gnueabihf-as。ld是链接器linker对应arm-linux-gnueabihf-ld。如果你还装了arm-linux-gnueabihf-gcc也可以直接一步到位arm-linux-gnueabihf-gcc -o hello hello.s -static。gcc会自动调用汇编器和链接器但实验教学里通常还是建议先用as和ld手动走一遍把每一阶段发生了什么看清楚。4.3 能直接“运行” hello.s 吗不能但你可以用这些方式运行编译产物这里再强调一次hello.s永远不能直接运行bash hello.s本身就是错误用法。但是编译链接之后的hello能不能在x86的Ubuntu上直接跑答案是不能因为它是ARM指令集x86 CPU没法原生执行。那怎么验证程序逻辑对不对有三种方式用QEMU用户态模拟qemu-arm ./hello适用于纯ARM指令、不依赖硬件外设的程序。用QEMU系统级模拟启动一个完整的ARM Linux虚拟机把编译产物拷贝进去运行。在真实ARM开发板上运行通过NFS、TFTP或者U盘把文件传过去执行。高校实验课最常见的做法是前两种。这里顺手提一句如果你机器上装了qemu-user执行sudo apt install qemu-user后甚至可以把hello作为“普通程序”直接运行qemu会在后端做指令翻译。实操心得很多教材里的ARM汇编程序其实是“裸机程序”用ld -Ttext 0x00000000指定了加载地址这种程序在QEMU用户态里不一定能跑因为它没有Linux系统调用接口。所以如果你的hello是用ld直接链接出来的裸机格式别急着怪qemu——先确认程序有没有依赖操作系统服务。4.4 chmod x 到底该不该用什么时候才真正需要我见过很多人遇到Permission denied时第一个反应是chmod x hello.s然后继续./hello.s结果冒出Exec format error。这个操作毫无意义因为.s文本文件即使有了执行权限也不是合法的可执行格式。但chmod x在什么场景下是真的有用呢如果你写的是一个shell脚本test.sh内容是#!/bin/bash开头那么chmod x test.sh ./test.sh是完全正确的。关键区别在于文件内容的第一行必须是指定解释器的shebang行且文件本身能被对应解释器读取。所以权限问题的正确解法不是给.s文件加执行权限而是把源文件汇编链接成真正的可执行文件再考虑给那个可执行文件加权限。如果你用ld生成的hello提示Permission denied那才需要chmod x hello——因为这时的hello是合法ELF程序只是文件权限位里没有执行位。这个区分非常重要我建议你把它抄在笔记里文本源码.s、.c、.sh不需要执行权限靠的是编译器/解释器去读取。编译产物ELF可执行文件需要读权限和执行权限内核才能加载它。4.5 一个容易被忽视的问题32位库缺失有些新手装完交叉工具链后发现arm-linux-gnueabihf-as可以跑但一执行arm-linux-gnueabihf-ld就报类似error while loading shared libraries: libc6-i386.so.6: cannot open shared object file的错误。为什么因为binutils-arm-linux-gnueabihf这个包里的二进制程序是为i386架构构建的它需要32位的动态库支持。而你的Ubuntu 20.04系统默认可能只安装了64位库。解决办法sudo apt install libc6-i386有些旧教程还会提到lib32z1、lib32stdc6其实在20.04上libc6-i386基本就能覆盖大部分情况。装上之后再跑ld问题就消失了。这个问题不算高频但一旦碰到特别隐蔽因为报错信息不会直接说“你缺32位库”而是绕弯子提示某个.so文件不存在。我的建议是看到.so相关的加载错误先file一下当前可执行文件再用ldd查看动态库依赖。5. 手动编译binutils的备用方案与避坑记录虽然不是首选但万一你的环境特殊比如公司内网没有apt源、需要定制binutils版本手动编译这条路也得会走。这里把要点写清楚。5.1 什么时候才真正需要走源码编译这条路我在前面反复强调apt优先但确实存在apt无法覆盖的场景你需要一个较新版本的binutilsUbuntu源里版本偏旧。你要给特定的CPU型号生成反汇编默认版本不支持某些扩展指令集。公司或学校的内网环境源不完整但你可以拿到源码包离线编译。你在搞交叉编译器for bare-metal裸机需要定制--targetarm-none-eabi这类配置选项。只要命中其中一条源码编译就是必要的。下面给一个最精简的编译流程。5.2 以目标平台 arm-linux-gnueabihf 为例的源码编译步骤先确保基础依赖齐全sudo apt install build-essential texinfo flex bisontexinfo是生成文档用的不加会影响make流程flex和bison是生成语法解析器用的binutils源码需要它们来生成汇编器和反汇编器的解析代码。然后下载源码。binutils的官方源码包可以从GNU官网镜像站获取或者使用wget直接拉取wget https://ftp.gnu.org/gnu/binutils/binutils-2.40.tar.gz tar -xzf binutils-2.40.tar.gz cd binutils-2.40接着创建独立的构建目录不要直接在源码目录里编译mkdir build-arm cd build-arm ../configure --targetarm-linux-gnueabihf --prefix/opt/arm-toolchain --disable-werror这里两个参数很关键--targetarm-linux-gnueabihf生成的工具链前缀就叫arm-linux-gnueabihf-。--prefix/opt/arm-toolchain指定安装目录避免装到系统路径污染全局环境。--disable-werror的作用是把“警告当错误”关掉。老版本源码在较新的gcc下编译时经常会有一些小警告如果不关掉这个选项make会直接失败这一项能救很多命。然后make -j4 sudo make install编译过程少则几分钟多则十几分钟取决于机器性能。装完之后可执行文件在/opt/arm-toolchain/bin/下。别忘了把路径加进PATHexport PATH/opt/arm-toolchain/bin:$PATH想永久生效写到~/.bashrc末尾。5.3 源码编译踩坑实录我遇到过的三个问题第一坑make报ERROR: no makefile。原因是忘了先../configure或者configure时当前目录不对。记住必须在build-arm目录里执行../configure不是在源码根目录里。这个设计是为了不污染源代码目录很多新手在源码根目录里直接 configure 也能跑但目录结构比较乱后续升级不便。第二坑编译中途报cannot find -lz。原因是缺少zlib的开发库执行sudo apt install zlib1g-dev再重新configure即可。第三坑make install提示权限不够。如果你没加sudo那/opt/arm-toolchain目录你根本写不进去。但如果加了sudo后续所有在这个目录里的操作都要注意权限归属。我的建议是make install不用 sudo直接把--prefix指向自己的家目录比如--prefix$HOME/opt/arm-toolchain省掉一堆权限问题。实测心得源码编译的binutils版本如果和系统gcc版本跨度太大链接器在后续使用中可能会选择不同的默认格式。这个问题在实验环境里很少见但一旦你开始做大型项目建议还是回到apt版本或者把工具链彻底独立到自定义目录避免混用。6. 常见报错排查速查表一条命令对应一个坑这里把我积累的典型报错整理成一个速查表方便你以后直接查。报错信息大概率原因解决动作arm-linux-objdump: command not found交叉工具链未安装或命令名不对安装binutils-arm-linux-gnueabihf或建立arm-linux-objdump软链接arm-linux-gnueabihf-objdump: command not found包没装成功或装了但PATH没包含检查dpkg状态检查/usr/bin目录加PATH/usr/bin/arm-linux-gnueabihf-objdump: No such file or directory通常是动态库缺失或文件格式不对用file看文件格式用ldd查依赖装libc6-i386bash: hello.s: Permission denied把汇编源码当可执行文件跑文件无执行权限按汇编→链接流程生成ELF文件不要直接运行.sbash: ./hello: cannot execute binary file: Exec format error二进制是ARM格式x86系统无法直接执行使用QEMU用户态模拟或移植到ARM设备执行arm-linux-gnueabihf-as: error: unrecognized option汇编源文件里有当前版本不支持的指令或伪指令检查CPU架构是否匹配或更新binutils版本undefined reference或file format not recognized链接时混用了x86的.o文件或库文件确认汇编和链接全用arm前缀工具不要混用本地gcc产物这张表覆盖了我能想到的绝大多数初学者会遇到的场景。如果你遇到的报错不在表里请先执行file查看目标文件类型再执行ldd查看依赖最后用dmesg | tail查看是否有内核拒绝加载的记录——这三板斧基本能解决90%的诡异问题。7. 最后的实操建议把流程固化成一串命令这篇文章写到这里核心内容已经讲完了。但我想在最后再分享一个我觉得真正有用的习惯把整个流程固化成一组命令存成一个shell脚本或写进笔记这样每次新开环境就不会再摸索一遍。以我的个人习惯为例我在一台新Ubuntu 20.04上搭建ARM汇编实验环境会依次执行sudo apt update sudo apt install -y binutils-arm-linux-gnueabihf qemu-user sudo ln -sf /usr/bin/arm-linux-gnueabihf-objdump /usr/local/bin/arm-linux-objdump sudo ln -sf /usr/bin/arm-linux-gnueabihf-as /usr/local/bin/arm-linux-as sudo ln -sf /usr/bin/arm-linux-gnueabihf-ld /usr/local/bin/arm-linux-ld echo export PATH/usr/local/bin:\$PATH ~/.bashrc这套命令做完arm-linux-objdump、arm-linux-as、arm-linux-ld三条命令全部可用而且qemu-user保证编译产物能模拟运行。整个过程不超过一分钟。对比我当年手动编译binutils的四十分钟差距非常明显。还有一个容易被忽略的习惯每次写完汇编源码先跑arm-linux-as -o hello.o hello.s确认汇编阶段没有语法错误再跑ld链接最后才用objdump验证。不要跳过任何一步。因为如果汇编阶段就有错误后面的链接和反汇编报错只会让你更抓狂。另外建议给hello.s文件开头加上一行注释写清楚它的目标架构和链接地址比如/* ARM32, ld -Ttext 0x10054 */。别小看这个习惯几个月后你翻回来看这段代码会感激这一行字帮你省了大量回忆时间。说实话我在实际踩坑过程中的最大体会是交叉编译环境的坑多数不是“技术深度”问题而是“信息错位”问题——你不知道某个包装完以后命令叫什么不知道某个文件该不该加执行权限不知道查看反汇编的对象应该是.o而不是.s。这三件事每一件单拎出来都不难但连在一起就容易让人绕圈子。希望这篇文章能帮你把这三个坑一次性填平后面再遇到类似报错对照着速查表查就行。
阅读完成 · 觉得有帮助?