You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

ARM64平台复制C函数至栈内存执行触发非法指令的问题排查

ARM64平台复制C函数至栈内存执行触发非法指令的问题排查

嘿,你遇到的这个问题其实是ARM64平台的几个特性加上代码里的小疏忽共同导致的,咱们一步步来拆解和解决:

1. 核心问题:函数长度计算完全错误

你声明的__start_myfnsection和__stop_myfnsection是uint64_t类型的数组,所以两者相减得到的是64位元素的个数,而不是函数的字节长度。你输出的9是元素数量,实际函数的字节长度应该是9*8=72字节!

复制长度不够的话,函数指令会被截断,执行到一半就会遇到非法指令,这就是你看到SIGILL的直接原因。

修复方法:把长度计算改成基于地址值的字节数计算,用uintptr_t强制转换地址后相减:

#include <stdint.h>
// ...
size_t myfn_length = (uintptr_t)__stop_myfnsection - (uintptr_t)__start_myfnsection;

2. ARM64强制要求指令地址4字节对齐

ARM64架构的AArch64状态下,所有执行的指令都是32位(4字节),因此指令地址必须是4字节对齐,否则会直接触发SIGILL非法指令信号。

你用__builtin_alloca分配的栈内存,虽然通常栈本身是16字节对齐的,但分配后的起始地址不一定保证4字节对齐(极端情况下可能出现不对齐的情况),所以需要手动调整:

修复方法:手动对齐内存地址:

// 先分配足够的内存(加上3字节的对齐余量)
void* memory = __builtin_alloca(myfn_length + 3);
// 调整到最近的4字节对齐地址
memory = (void*)(((uintptr_t)memory + 3) & ~3UL);

3. PIE编译带来的PC相对寻址问题

你用了-pie编译选项,生成的是位置无关可执行文件(PIE)。你的函数myfn里访问了static变量i,这个访问是通过PC相对偏移实现的——也就是基于当前指令的PC地址计算变量的位置。

当你把函数复制到栈上后,函数的PC地址和原来的.text段位置完全不同,原来的偏移就会指向错误的内存地址,后续访问i的时候必然会崩溃(甚至可能在执行到那条指令时出错)。

解决方案:

  • 测试阶段可以暂时去掉-pie编译选项,生成非位置无关的可执行文件,这样访问static变量用的是绝对地址,复制后仍然有效(注意:现代系统可能默认开启PIE,需要显式加-no-pie)。
  • 如果必须用PIE,那需要手动修改函数中的寻址逻辑,把static变量也复制到栈上并调整偏移,这会非常复杂,不建议用于简单测试。

4. 验证栈的执行权限

虽然你加了-z execstack,但可以用readelf工具验证栈段是否真的有执行权限:

$ readelf -l ./a.out | grep GNU_STACK

输出中如果GNU_STACK段的Flags包含RWE(Read-Write-Execute),说明权限设置正确;如果只有RW,说明编译选项没生效,需要检查gcc版本或系统配置。

修复后的完整代码示例

把上面的修复点整合后,代码应该是这样的:

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>

extern uint64_t __start_myfnsection[];
extern uint64_t __stop_myfnsection[];

__attribute__((noinline, section("myfnsection"))) void myfn() {
    static int i;
    i++;
    printf("I love lemons!^^.\n");
    printf("i=%d\n", i);
    return;
}

int main () {
    myfn();

    // 正确计算函数字节长度
    size_t myfn_length = (uintptr_t)__stop_myfnsection - (uintptr_t)__start_myfnsection;
    printf("Length of myfn() function is: %zu\n", myfn_length);

    // 分配栈内存并对齐到4字节
    void* memory = __builtin_alloca(myfn_length + 3);
    memory = (void*)(((uintptr_t)memory + 3) & ~3UL);
    memcpy(memory, __start_myfnsection, myfn_length);

    void (*myfn_copy)() = memory;
    myfn_copy();

    return 0;
}

编译命令(去掉PIE用于测试):

$ gcc -no-pie -z execstack -fno-stack-protector -g test.c

运行后应该能正常输出两次函数内容,i的值分别为1和2,不会再触发非法指令。

备注:内容来源于stack exchange,提问作者Elisa K. K.

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.15 10:13:10