RISC-V汇编中execve系统调用失败返回242错误码排查
- 开发环境:从GitHub获取RISC-V工具链,使用以下命令完成配置:
./configure /opt/rv32 ./configure --prefix=/opt/rv32 --with-arch=rv32ia --with-abi=ilp32
- 运行环境:通过
qemu-user提供的qemu-riscv32命令运行编译生成的32位RISC-V二进制程序。 - 目标:手写汇编代码实现
execve系统调用,启动/bin/sh。
首先编写了功能等价的C代码作为对照,代码如下:
#include <unistd.h> char *args[] = {"/bin/sh", 0x0}; int main() { execve(args[0], args, 0x0); }
使用-static参数静态编译后,对二进制文件反汇编得到核心汇编逻辑:
00010530 <main>: 10530: ff010113 addi sp,sp,-16 10534: 00112623 sw ra,12(sp) 10538: 00812423 sw s0,8(sp) 1053c: 01010413 addi s0,sp,16 10540: a9418793 addi a5,gp,-1388 # 85df0 <args> 10544: 0007a703 lw a4,0(a5) 10548: 00000613 li a2,0 1054c: a9418593 addi a1,gp,-1388 # 85df0 <args> 10550: 00070513 mv a0,a4 10554: 304160ef jal ra,26858 <__execve> 10558: 00000793 li a5,0 1055c: 00078513 mv a0,a5 10560: 00c12083 lw ra,12(sp) 10564: 00812403 lw s0,8(sp) 10568: 01010113 addi sp,sp,16 1056c: 00008067 ret 00026858 <__execve>: 26858: 0dd00893 li a7,221 2685c: 00000073 ecall 26860: fffff8b7 lui a7,0xfffff 26864: 00a8e463 bltu a7,a0,2686c <__execve+0x14> 26868: 00008067 ret 2686c: 71d0406f j 2b788 <__syscall_error>
该C程序无论是否添加-static编译参数都可以正常运行。
参照反汇编结果编写的手写汇编代码如下:
.globl _start .section .text _start: # execve syscall la a0, shell # Pointer to '/bin/sh' la a1, addr # Pointer to the arraythat contains '/bin/sh' sw a0, 0(a1) mv a2, x0 # No environment variables are needed li a7, 0xDD # 221 ecall # exit syscall li a7, 0x5D # 93 ecall .section .rodata shell: .string "/bin/sh" .section .data addr: .space 4
使用以下命令编译汇编代码:
riscv32-unknown-linux-gnu-gcc -c syscall.s && riscv32-unknown-linux-gnu-ld syscall.o -o syscall
运行时execve系统调用失败,返回错误码242,在标准errno列表中找不到242对应的错误说明。
核心错误原因
argv数组不符合系统调用规范
POSIX标准明确要求,execve的第二个参数argv是一个以NULL指针结尾的字符串指针数组,数组长度至少为2(第一个元素是程序路径,最后一个元素是NULL哨兵)。
你的代码中addr: .space 4仅预留了4字节空间,仅够存储1个32位指针,完全没有位置存放必须的末尾NULL值;你仅向该地址写入了/bin/sh的指针,后续内存值是未定义的非0值,内核遍历argv列表时读不到终止标记,会访问非法内存地址,触发EFAULT(错误码14,含义是坏地址)错误。错误码242的来源
RISC-V架构Linux系统调用的错误返回规则为:ecall返回后,如果a0寄存器的值落在[-4095, -1]区间(无符号32位表示为0xFFFFF001 ~ 0xFFFFFFFF),代表系统调用失败,返回值的绝对值就是errno。
你看到的242是错误返回值-EFAULT(补码为0xFFFFFFF2)被截断低8位后的结果(0xF2=242),自然无法在errno表中找到242对应的条目。反汇编代码中__execve函数的判断逻辑lui a7,0xfffff; bltu a7,a0 <__syscall_error>就是在做这个错误区间判断。
修复后的可运行代码
直接在定义argv数组时完成初始化,避免运行时写入出错,同时保证数组以NULL结尾:
.globl _start .section .text _start: # execve系统调用,调用号221 la a0, shell # a0 = 待执行程序路径"/bin/sh" la a1, argv # a1 = argv参数数组首地址 mv a2, x0 # a2 = envp = NULL,不传入环境变量 li a7, 221 ecall # execve仅在调用失败时返回,执行exit退出,调用号93 li a0, 1 # 退出码1代表执行失败 li a7, 93 ecall .section .rodata shell: .string "/bin/sh" argv: .word shell # argv[0] = 程序路径指针 .word 0 # argv数组必须以NULL指针作为结尾标记
使用原编译命令重新编译后即可正常运行,成功启动/bin/sh。
内容的提问来源于stack exchange,提问作者Josep

