从汇编调用的C函数调用printf时出现分段错误



我尝试在Linux上的x86-64 Assembler中实现快速排序。由于我对它还不完全满意,所以我用C编写了分区算法。它似乎可以工作,但必须关闭一些东西,因为在我的C代码中添加对fprintf的调用(Asm调用)会导致段错误。我想我一定是弄乱了堆栈,或者没有遵守调用约定,但我不知道在哪里。

值得注意的是,只有在向printf传递额外参数时才会发生段错误。如果只传递格式字符串,则工作正常。

有两个文件:quicksort.squicksort.c。我在Ubuntu 20.04.1下的GCC 9.4.0上编译它们,使用gcc quicksort.c quicksort.s -o quicksort命令,然后使用./quicksort运行生成的程序。

我首先用C写代码:

void quicksort(int* arr, size_t n) {
if (n <= 1) return;
int p = partition(arr, n);
quicksort(arr, p);
p += 1;
quicksort(arr+p, n-p);
}

然后将其翻译成x86-64汇编程序,得到如下:

# quicksort.s
# Takes an array of integers in %rsi
# Takes the size of the array in %rsi
.global quicksort
quicksort:
cmpq $1, %rsi
jle early_exit
pushq %rbp
movq %rsp, %rbp
# rbx, r12 and r13 are callee-saved
pushq %rbx
pushq %r12
pushq %r13
# put arguments in callee-saved registers for later use
movq %rdi, %rbx
movq %rsi, %r12
# p = partition(arr, n)
call partition
# put result of partition in callee-saved register for later use
movq %rax, %r13
# quicksort(arr, p)
movq %rbx, %rdi
movq %r13, %rsi
call quicksort
# p += 1
addq $1, %r13
# quicksort(arr+p*sizeof(int), n-p)
leaq (%rbx, %r13, 4), %rdi
movq %r12, %rsi
subq %r13, %rsi
call quicksort
popq %r13
popq %r12
popq %rbx
movq %rbp, %rsp
popq %rbp
early_exit:
ret
下面是它调用的C代码:
// quicksort.c
#include <stdio.h>
#include <stddef.h>
#include <stdlib.h>
void quicksort(int* arr, size_t n);
size_t partition(int* arr, size_t n) {
// commenting the next line fixes the segfault
fprintf(stderr, "%d", 10);
size_t l = 1;
size_t r = n;
int p = arr[0];
while (l < r) {
while (l < r && arr[l] <= p) l++;
while (l < r && arr[r-1] > p) r--;
if (l == r) break;
int temp = arr[l];
arr[l] = arr[r-1];
arr[r-1] = temp;
l++;
r--;
}
arr[0] = arr[r-1];
arr[r-1] = p;
return r-1;
}
int main() {
#define LEN 100
int arr[LEN] = { };
for (int i = 0; i < LEN; ++i)
arr[i] = rand();
quicksort(arr, LEN);
for (int i = 0; i < LEN; ++i)
printf(" %d", arr[i]);
putchar('n');
}

您没有保持堆栈对齐。ABI说%rsp必须总是call指令前面16的倍数。call本身推入一个8字节的数量(返回地址),因此堆栈指针在函数入口总是与8 (mod 16)一致,并且在您进行另一次调用之前修复它是您的责任。

只有在对fprintf的调用没有注释时才会导致崩溃,因为fprintf实际上正在做一些利用这个ABI需求的事情(具体来说,使用一些x86-64向量指令,可能是为了加速二进制到十进制的转换)。partition本身不会做任何关心的事情。

修复它的最简单方法是丢弃帧指针。在x86-64上不需要这样做,这样你将推送奇数个寄存器,这给了你正确的堆栈对齐作为副作用。

您的堆栈在call partition中没有正确对齐,这意味着它在fprintf中也不会正确对齐,这是该函数所依赖的(因为ABI赋予每个函数的权利)。在此之前执行sub $8, %rsp或另一个假push来对齐它,然后撤消它。

最新更新