本文记录使用命令 OHOS_ARCH=aarch64 OHOS_ABI=arm64-v8a sh ./create-hnp.sh 构建 Elf-loader(安装为 loader)的完整过程,包括环境、构建链路、关键日志、常见问题与解决方案、产物验证与重建方法,便于复现与运维。

📖 Elf-loader 简介

Elf-loader 是一个轻量级的 ELF(Executable and Linkable Format)文件加载器,用于在用户空间加载和执行 ELF 格式的可执行文件。它提供了一个最小化的 ELF 加载实现,不依赖标准 C 库,适合在受限环境中使用。

🎯 Elf-loader 的作用与重要性

Elf-loader 是 ELF 文件加载的核心工具,提供了:

  • ELF 文件加载:在用户空间加载和执行 ELF 格式的可执行文件
  • 轻量级实现:不依赖标准 C 库,使用系统调用直接与内核交互
  • 独立运行:可以作为独立的加载器运行,不依赖动态链接器
  • 研究工具:适合用于 ELF 文件格式研究和实验
  • 调试工具:可以用于调试和分析 ELF 文件的加载过程
  • 教学工具:帮助理解 ELF 文件格式和加载机制

🔧 Elf-loader 核心特性

1. ELF 文件格式支持
  • ELF 头解析:解析 ELF 文件头(Elf_Ehdr)
  • 程序头解析:解析程序头表(Elf_Phdr)
  • 段加载:加载可执行段到内存
  • 符号解析:解析符号表和重定位信息
2. 内存管理
  • 内存映射:使用 mmap 系统调用映射内存
  • 内存保护:使用 mprotect 设置内存保护标志
  • 内存释放:使用 munmap 释放映射的内存
  • JIT 支持:通过 prctl 系统调用支持 JIT 编译
3. 系统调用封装
  • 文件操作openreadlseekclose
  • 内存操作mmapmunmapmprotect
  • 进程控制exitprctl
  • 错误处理errno 处理
4. 架构支持
  • aarch64:ARM 64位架构支持
  • amd64/x86_64:x86-64 架构支持
  • 交叉编译:支持交叉编译到目标架构
5. 应用场景
  • ELF 文件研究:研究 ELF 文件格式和加载机制
  • 调试工具:调试 ELF 文件的加载过程
  • 教学工具:教学 ELF 文件格式知识
  • 实验环境:在受限环境中加载和执行 ELF 文件

🚀 构建入口与环境

  • 📝 执行命令OHOS_ARCH=aarch64 OHOS_ABI=arm64-v8a sh ./create-hnp.sh
  • 🔧 入口脚本create-hnp.sh
    • 检查必需的环境变量 OHOS_ARCHOHOS_ABI
    • 导出 LC_CTYPETOOL_HOMEOHOS_SDK_HOME
    • 执行 make -C build-hnp
  • 📦 顶层构建build-hnp/Makefile
    • PKGS 变量定义需要构建的包列表(包含 elf-loader
    • 通过 check-pkgs 机制自动检测 PKGS 变化并触发重新构建
    • 自动合并 external-hnp 目录下的外部 HNP 包
    • base.hnp 依赖所有包的 .stamp 和外部 HNP 包
    • 总目标 all: copy,打包 base.hnp 并拷贝到 entry/hnp/$(OHOS_ABI)

⚙️ Elf-loader 包的构建配置

  • 📁 包目录build-hnp/elf-loader/Makefile
    • 继承通用规则:include ../utils/Makefrag
    • 源地址:https://github.com/MikhailProg/elf(Git 仓库)
    • 版本:最新主分支
  • 🔧 补丁应用
    • jit.patch - 添加 JIT 编译支持
      • loader.c 中添加 prctl 调用以支持 JIT
      • z_syscalls.c 中添加 prctl 系统调用封装
      • z_syscalls.h 中添加 z_prctl 函数声明
  • ⚙️ 构建参数
    • aarch64 架构
      • ARCH=aarch64
      • CFLAGS="-ffreestanding -DELFCLASS=ELFCLASS64"
    • x86_64 架构
      • ARCH=amd64
      • CFLAGS="-ffreestanding -DELFCLASS=ELFCLASS64"
  • 🔨 构建流程
    1. 从 GitHub 克隆源码到 download/elf
    2. 复制源码到 temp/elf
    3. 应用 jit.patch 补丁
    4. 根据架构编译(make -C src ARCH=...
    5. 复制 loader 二进制到 build/bin/
    6. 复制到 ../sysroot
  • 🔧 通用工具链与路径build-hnp/utils/Makefrag
    • CC/CXX/LD/AR/RANLIB/... 均指向 OHOS SDK 的 LLVM 工具链
    • 使用交叉编译器:$(OHOS_ARCH)-unknown-linux-ohos-clang

📋 关键日志与过程节点

  • 📥 源码获取
    • 从 GitHub 克隆 https://github.com/MikhailProg/elf 仓库
    • 克隆到 download/elf 目录
  • 🔧 补丁应用
    • 复制源码到 temp/elf
    • 应用 jit.patch 补丁(添加 JIT 支持)
  • ⚙️ 编译阶段
    • 根据架构选择编译参数(ARCH=aarch64ARCH=amd64
    • 使用 -ffreestanding 标志(不依赖标准库)
    • 定义 ELFCLASS64 宏(64位 ELF 文件)
    • 编译生成 loader 二进制文件
  • 📦 安装与打包
    • 复制 loaderbuild/bin/
    • 复制到 ../sysroot/bin/
    • 完成 base.hnp 重打包,拷贝产物到 entry/hnp/arm64-v8a/
    • Elf-loader 工具已成功打包到 base.hnp

✅ 产物验证

📦 检查打包文件

ls build-hnp/base.hnp  # 应存在
ls entry/hnp/arm64-v8a/*.hnp  # 应包含 base.hnp 与 base-public.hnp

🔍 检查二进制文件

# 检查 Elf-loader 可执行文件
ls -lh build-hnp/sysroot/bin/loader
file build-hnp/sysroot/bin/loader

✅ 构建验证结果

  • ✅ Elf-loader 可执行文件已安装:
    • loader (9.9K) - ELF 文件加载器
  • ✅ 文件类型:ELF 64-bit LSB pie executable, ARM aarch64
  • ✅ 动态链接:dynamically linked, interpreter /lib/ld-musl-aarch64.so.1
  • ✅ 已剥离符号:stripped
  • ✅ 已打包到 base.hnp

💻 终端中执行的示例命令

🔧 Elf-loader 基本使用

1. 基本加载
# 加载并执行 ELF 文件
loader /path/to/executable

# 加载并执行当前目录的 ELF 文件
loader ./myapp

# 加载并执行系统命令(如果支持)
loader /bin/ls

# 检查 loader 本身
file loader
loader loader  # 尝试加载 loader 自身
2. ELF 文件分析
# 使用 readelf 分析 ELF 文件
readelf -h /path/to/executable

# 查看程序头
readelf -l /path/to/executable

# 查看节头
readelf -S /path/to/executable

# 查看符号表
readelf -s /path/to/executable

# 查看动态链接信息
readelf -d /path/to/executable

image-20251126161624802

3. 使用 objdump 分析
# 反汇编 ELF 文件
objdump -d /path/to/executable

# 查看文件头信息
objdump -f /path/to/executable

# 查看节信息
objdump -h /path/to/executable

# 查看符号表
objdump -t /path/to/executable

# 查看动态符号表
objdump -T /path/to/executable

image-20251126161727266

4. ELF 文件验证
# 检查 ELF 文件类型
file /path/to/executable

# 检查 ELF 文件架构
readelf -h /path/to/executable | grep Machine

# 检查 ELF 文件入口点
readelf -h /path/to/executable | grep Entry

# 检查 ELF 文件是否可执行
test -x /path/to/executable && echo "Executable" || echo "Not executable"

# 检查 ELF 文件依赖
ldd /path/to/executable 2>/dev/null || echo "Static or not a dynamic executable"
5. 创建测试 ELF 文件
# 创建一个简单的 C 程序
cat > test.c << 'EOF'
#include <stdio.h>
int main() {
    printf("Hello from ELF loader!\n");
    return 0;
}
EOF

# 编译为静态链接的可执行文件
gcc -static -o test test.c

# 使用 loader 加载
./loader ./test

# 编译为动态链接的可执行文件
gcc -o test_dynamic test.c

# 使用 loader 加载(可能需要动态链接器支持)
./loader ./test_dynamic
6. 调试 ELF 加载
# 使用 strace 跟踪系统调用
strace ./loader /path/to/executable

# 跟踪特定的系统调用
strace -e mmap,munmap,mprotect ./loader /path/to/executable

# 使用 gdb 调试
gdb ./loader
(gdb) set args /path/to/executable
(gdb) run

# 使用 readelf 查看加载信息
readelf -l /path/to/executable | grep LOAD
7. ELF 文件格式分析
# 查看 ELF 文件头详细信息
readelf -h /path/to/executable

# 查看程序头表
readelf -l /path/to/executable

# 查看节头表
readelf -S /path/to/executable

# 查看符号表
readelf -s /path/to/executable

# 查看重定位信息
readelf -r /path/to/executable

# 查看动态段
readelf -d /path/to/executable

# 查看注释段
readelf -n /path/to/executable
8. 实际应用示例
# 加载简单的静态链接程序
cat > hello.c << 'EOF'
#include <unistd.h>
int main() {
    write(1, "Hello, World!\n", 14);
    return 0;
}
EOF

gcc -static -nostdlib -o hello hello.c
./loader ./hello

# 加载使用系统调用的程序
cat > syscall_test.c << 'EOF'
#include <unistd.h>
#include <sys/syscall.h>
int main() {
    syscall(SYS_write, 1, "Syscall test\n", 13);
    return 0;
}
EOF

gcc -static -o syscall_test syscall_test.c
./loader ./syscall_test

# 测试不同架构的 ELF 文件
# 注意:loader 只能加载匹配架构的 ELF 文件
file /path/to/executable  # 检查架构
./loader /path/to/executable  # 加载匹配架构的文件
9. ELF 文件比较
# 比较两个 ELF 文件
diff <(readelf -h file1) <(readelf -h file2)

# 比较程序头
diff <(readelf -l file1) <(readelf -l file2)

# 比较符号表
diff <(readelf -s file1) <(readelf -s file2)

# 使用 hexdump 查看二进制内容
hexdump -C /path/to/executable | head -20

# 查看 ELF 文件大小
ls -lh /path/to/executable
10. 功能验证脚本
#!/bin/bash
# Elf-loader 工具验证脚本

LOADER_BIN="build-hnp/sysroot/bin"

echo "=== Elf-loader 工具验证 ==="

# 检查可执行文件
echo ""
echo "=== 可执行文件验证 ==="
if [ -f "$LOADER_BIN/loader" ]; then
    echo "✓ loader: 存在"
    file "$LOADER_BIN/loader"
    echo "  文件大小: $(ls -lh "$LOADER_BIN/loader" | awk '{print $5}')"
    echo "  架构信息: $(file "$LOADER_BIN/loader" | grep -o "ARM aarch64")"
    echo "  链接类型: $(file "$LOADER_BIN/loader" | grep -o "dynamically linked\|statically linked")"
    
    # 检查 ELF 文件头
    echo ""
    echo "=== ELF 文件头信息 ==="
    readelf -h "$LOADER_BIN/loader" 2>/dev/null | head -15
    
    # 检查程序头
    echo ""
    echo "=== 程序头信息 ==="
    readelf -l "$LOADER_BIN/loader" 2>/dev/null | head -10
else
    echo "✗ loader: 缺失"
fi

# 测试基本功能(需要在目标设备上运行)
echo ""
echo "=== 功能测试(需要在目标设备上运行)==="
echo "加载 ELF 文件:"
echo "  $LOADER_BIN/loader /path/to/executable"
echo ""
echo "检查 loader 自身:"
echo "  file $LOADER_BIN/loader"
echo "  readelf -h $LOADER_BIN/loader"
echo ""
echo "测试加载简单程序:"
echo "  # 创建测试程序"
echo "  echo 'int main(){return 0;}' > test.c"
echo "  gcc -static -o test test.c"
echo "  $LOADER_BIN/loader ./test"

🐛 构建过程遇到的问题及解决方法

❌ 问题 1:GitHub 克隆失败

  • 🔍 症状:无法从 GitHub 克隆源码仓库
  • 🔎 原因:网络问题或 GitHub 访问受限
  • ✅ 解决方法
    • 手动克隆仓库到 build-hnp/elf-loader/download/elf 目录
    • 使用代理或镜像站点克隆
    • 确保 Git 已正确安装和配置
    • 位置:build-hnp/elf-loader/Makefile:20-22

❌ 问题 2:补丁应用失败

  • 🔍 症状git apply jit.patch 失败
  • 🔎 原因:补丁文件与源码版本不匹配或补丁文件不存在
  • ✅ 解决方法
    • 检查 jit.patch 文件是否存在
    • 确保补丁文件与源码版本匹配
    • 如果补丁不匹配,可以手动应用补丁或更新补丁文件
    • 位置:build-hnp/elf-loader/Makefile:7

❌ 问题 3:架构不支持

  • 🔍 症状Unsupported OHOS_ARCH=
  • 🔎 原因:传入的架构不在支持列表中
  • ✅ 解决方法
    • 确保传入支持架构(aarch64x86_64
    • 对于 x86_64,使用 ARCH=amd64
    • 位置:build-hnp/elf-loader/Makefile:8-15

❌ 问题 4:编译标志错误

  • 🔍 症状:编译失败或生成错误的二进制文件
  • 🔎 原因CFLAGS 设置不正确或与源码不兼容
  • ✅ 解决方法
    • 确保 -ffreestanding 标志正确设置(不依赖标准库)
    • 确保 -DELFCLASS=ELFCLASS64 正确设置(64位 ELF)
    • 检查源码是否支持这些编译标志
    • 位置:build-hnp/elf-loader/Makefile:9,11

❌ 问题 5:链接错误

  • 🔍 症状:链接时出现未定义符号错误
  • 🔎 原因:链接标志不正确或缺少必要的对象文件
  • ✅ 解决方法
    • 检查链接标志:-nostartfiles -nodefaultlibs -nostdlib
    • 确保所有必要的对象文件都包含在链接命令中
    • 检查入口点设置:-e z_start
    • 位置:上游 Makefile(src/Makefile

❌ 问题 6:JIT 支持问题

  • 🔍 症状:JIT 相关功能不工作
  • 🔎 原因prctl 系统调用不支持或补丁未正确应用
  • ✅ 解决方法
    • 确保 jit.patch 已正确应用
    • 检查 prctl 系统调用是否在目标系统上可用
    • 如果不需要 JIT 支持,可以移除补丁
    • 位置:build-hnp/elf-loader/Makefile:7build-hnp/elf-loader/jit.patch

❌ 问题 7:ELF 文件加载失败

  • 🔍 症状:loader 无法加载某些 ELF 文件
  • 🔎 原因:ELF 文件格式不支持或依赖缺失
  • ✅ 解决方法
    • 检查 ELF 文件格式是否支持(64位、匹配架构)
    • 检查 ELF 文件是否依赖动态链接器(loader 可能不支持)
    • 尝试使用静态链接的 ELF 文件
    • 检查 ELF 文件是否使用了 loader 不支持的特性

❌ 问题 8:交叉编译环境问题

  • 🔍 症状:交叉编译器未找到或路径不正确
  • 🔎 原因:环境变量未正确设置或工具链路径不正确
  • ✅ 解决方法
    • 确保通过 create-hnp.sh 触发构建以获得完整环境变量
    • 检查 OHOS_SDK_HOME 是否正确设置
    • 确保交叉编译器路径正确:$(OHOS_SDK_HOME)/native/llvm/bin/$(OHOS_ARCH)-unknown-linux-ohos-clang
    • 位置:build-hnp/utils/Makefrag:10

🔄 重建与扩展

  • 🔧 重建单包

    make -C build-hnp rebuild-elf-loader  # 触发子包重新编译并刷新 .stamp
    
  • 🧹 清理

    make -C build-hnp clean  # 清理 sysroot、所有 .stamp 和 PKGS_MARKER
    
  • 📦 扩展:Elf-loader 是 ELF 文件加载的核心工具,适合用于研究和实验

  • 🔄 自动重建机制

    • 修改 PKGS 后,check-pkgs 会自动检测变化并触发重新构建
    • 新增外部 HNP 包到 external-hnp 目录后,会自动合并到 base.hnp

💡 实践建议

  • 🔧 构建配置:确保补丁正确应用,特别是 JIT 支持补丁
  • 🚀 使用场景:Elf-loader 适合用于 ELF 文件格式研究和实验
  • 📦 依赖管理:注意 loader 不依赖标准 C 库,使用系统调用直接与内核交互
  • 🔗 测试建议:在受控环境下测试,确保 ELF 文件格式和架构匹配
  • 🌐 注意事项:loader 的行为依赖目标设备的内核和 /proc//dev 等接口

📝 结论与建议

  • ✅ 本次已在 aarch64 环境下完成 Elf-loader 的交叉编译与打包,可执行文件已安装到 sysroot 并纳入 HNP 包。
  • 💡 为保证构建稳定
    • 使用简单的 Make 构建系统,配置清晰
    • 应用补丁添加 JIT 支持
    • 使用 -ffreestanding 标志确保不依赖标准库
    • 确保通过 create-hnp.sh 触发构建以获得完整环境变量
    • 利用 check-pkgs 机制自动检测包列表变化,无需手动清理
    • Elf-loader 为 ELF 文件加载提供了轻量级的实现
    • 常见陷阱包括 GitHub 克隆失败、补丁应用失败、架构不支持;当前已通过构建配置和补丁处理
    • 建议在受控环境下测试,确保 ELF 文件格式和架构匹配
    • 构建过程简洁,Make 交叉参数与补丁应用清晰,产物安装路径明确
    • 产物开箱即用,适合在设备上进行基础的 ELF 加载实验与研究

📚 以上为 Elf-loader 构建的深度解读与实践记录。

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐