AArch64汇编数组段错误排查及优化
2025-01-11 02:56:21
AArch64 汇编数组操作导致段错误排查
问题根源分析
在 AArch64 汇编中操作数组时,段错误是比较常见的问题,根本原因常常归结于对内存的非法访问。具体到上面提供的代码,段错误发生在使用getchar
读取字符后,向数组存储数据之时,这意味着数组访问方面可能存在一些隐患。
提供的代码示例中,分配了 256
字节的空间,但是使用过程中,我们无法保证用户输入的长度是可控的,所以要考虑几个关键点:数组边界、存储方式以及寄存器使用。
这段汇编代码,核心问题在 input_loop
的逻辑,看起来没有太多错误,但深入分析后就会发现以下潜在问题:
check
函数调用: check函数没有必要,直接将cmp x4, #0;beq compute_checksum
挪到主循环里,可以省去函数调用的开销和逻辑跳转的复杂性;并且,check函数存在潜在错误,每次input_loop
结束前执行bl check
,而 check 内只是比较x4
寄存器是否为 0 ,而无论什么结果都会进入 input_loop,循环是不会终止的;- 数组溢出: 初始定义了一个
256
字节的数组空间。但是循环过程中只检查用户是否输入 EOF, 并没有数组的越界判断,所以存在用户输入字符数大于256
导致缓冲区溢出的可能性。 getchar
的返回值 : getchar 读取单个字符到 w0 寄存器,但其返回值也有可能不是有效字符,需考虑其出错时的边界值问题。- 寄存器保存: 在调用函数 (
printf
,getchar
) 时,可能会用到一些寄存器,这些寄存器如果未经保存就直接修改,可能会导致数据错误。虽然在这段代码中体现的不是很明显,但它是通用编程的注意事项。
解决方案
以下是一些针对性地解决段错误的方案。
1. 修正输入判断并增加边界检查
要避免缓冲区溢出,必须在读取每个字符后添加对数组边界的检查。可以简单地用一个变量 x7
表示数组的长度限制,并且,当输入的字符达到上限后停止循环。 getchar
返回 EOF 或者其它错误,我们也应该提前退出。
.data
array: .space 256
askC: .asciz "Enter a char value: "
fmt_check: .asciz "Checksum: %ld\n"
.text
.global main
main:
mov x4, #10 // 设置最大读取字符数
ldr x5, =array // x5 指向数组起始地址
mov x6, #0 // x6 用作校验和累加器
mov x7, #256 // x7 表示数组容量大小
input_loop:
ldr x0, =askC
bl printf // 提示用户输入字符
bl getchar // 读取一个字符到 w0
cmp w0, #0 // 检查 EOF 或输入错误
beq end // 如果出错退出
cmp x5, #array+256 // 比较当前指针和数组末尾
beq compute_checksum // 数组已满,计算校验和并结束
strb w0, [x5] // 将读取的字符存储在数组中
uxtw x0, w0 // 将 w0 中的字符转换为 x0,并零扩展
add x6, x6, x0 // 计算校验和
add x5, x5, #1 // 数组指针后移
sub x4, x4, #1 // 将计数器减 1
cmp x4,#0
bne input_loop // 输入字符数量达到上限或者数组已经装满,跳转到校验和计算
compute_checksum:
ldr x0, =fmt_check // 加载格式化字符串地址
mov x1, x6 // 设置校验和为 printf 的第二个参数
bl printf // 打印校验和
end:
mov x0, #0 // 返回状态码 0
ret
此修改确保了读取数据不超过分配数组的大小,在input_loop
中检查数组边界(cmp x5, #array+256;beq compute_checksum
)。
以及检查用户输入是否错误 cmp w0, #0;beq end
。
每次循环,检查循环次数的限制(sub x4, x4, #1 ;cmp x4,#0
) 。
2. 寄存器保存(虽然这段代码示例不太必要)
尽管提供的代码段在当前情况下不会因为寄存器冲突导致段错误,但良好的实践是在调用任何外部函数(例如printf
或 getchar
)之前保存可能会被修改的寄存器。以下是如何保存和恢复寄存器(并非绝对必须,为代码规范):
// ... (先前代码)
input_loop:
// 保存需要保护的寄存器,这里x4-x7和lr(返回地址)可能被使用到,视函数情况而定
stp x4,x5,[sp,#-16]!
stp x6,x7,[sp,#-16]!
mov x9, lr
stp x9, [sp, #-16]!
ldr x0, =askC
bl printf
bl getchar
// 恢复被调用方可能破坏的寄存器
ldp x9, [sp], #16
mov lr, x9
ldp x6,x7,[sp],#16
ldp x4,x5,[sp],#16
cmp w0, #0
beq end
//...(剩余代码,处理校验和)
end:
mov x0, #0
ret
我们使用 stp
(store pair)和 ldp
(load pair)来将寄存器推入堆栈和弹出,这是一种常见的方式,可以有效管理寄存器的保存与恢复,并且通过 mov x9, lr
的方式保存链接寄存器lr
的值。这段代码在本次问题的上下文环境中是多余的,但是建议了解这些概念,提高代码的稳定性。
操作步骤:
- 保存 代码到例如
checksum.s
的文件中。 - 汇编 文件:
as checksum.s -o checksum.o
- 链接 文件:
ld checksum.o -o checksum
- 执行 :
./checksum
用户将被提示输入字符,输入的字符达到数量或者缓冲区填满或者用户输入eof
时候计算校验和。
总结
解决 AArch64 汇编中的段错误,关键在于细致地检查内存操作和寄存器使用。数组越界、寄存器使用冲突都是潜在问题。编写代码时要认真地分析和解决这些问题,同时学习正确的寄存器使用规则。通过这种严谨的习惯,可以写出更健壮,安全的应用。
(本文并不提供外部资源,所有方法均可在ARM官方网站文档查到)。