Linux x86-64下C与NASM双向调用链接失败求助
我正在学习汇编编程,想实现C和NASM的双向调用:C主函数调用NASM函数,该NASM函数再调用C函数,但链接运行后出现段错误。
我的代码
main.c
#include <stdio.h> #include <inttypes.h> int64_t entry(); void printr() { printf("Printing random stuff."); } int main(int argc, char** argv) { int64_t result = entry(); printf("%" PRId64 "\n", result); return 0; }
tri_tail_rec.s
default rel section .text global entry entry: mov rbx, 10 ; 输入值 mov rax, 0 ;;; tri: 计算第n个三角数的尾递归函数,n存储在rbx中 tri: cmp rbx, 0 je done add rax, rbx sub rbx, 1 jmp tri done: extern printr call printr ret
尝试的编译链接命令
- 使用gcc链接:
nasm -f elf64 -o tri_tail_rec.o tri_tail_rec.s cc -c -o main.o main.c gcc main.o tri_tail_rec.o -o tri_tail_rec
运行后出现段错误。
- 使用ld直接链接:
ld main.o tri_tail_rec.o -lc -I /lib64/ld-linux-x86-64.so.2 -o tri_tail_rec
得到警告ld: warning: cannot find entry symbol _start; defaulting to 0000000000401020,运行后同样段错误。
背景
之前我成功实现了C调用NASM函数的单方向调用,代码如下:
tri.s
global entry default rel section .text entry: mov rbx, 36 ; 输入值 ;;; tri: 计算第n个三角数的递归函数,n存储在rbx中 tri: cmp rbx, 0 ; 如果rbx为0则结束 je done push rbx ; 保存rbx sub rbx, 1 call tri ; 计算tri(rbx-1),结果存在rax中 pop rbx ; 恢复rbx add rax, rbx ; 结果为rbx+tri(rbx-1) ret done: ; 基准情况跳转处 mov rax, 0 ; 返回0 ret
main.c
#include <stdio.h> #include <inttypes.h> int64_t entry(); int main(int argc, char** argv) { int64_t result = entry(); printf("%" PRId64 "\n", result); return 0; }
编译链接命令:
nasm -f elf64 -o tri.o tri.s gcc -c main.c -o main.o gcc main.o tri.o -o tri ./tri
这段代码运行完全正常,但加上NASM调用C函数后就出现了问题。
问题原因与解决方案
段错误的核心原因是违反了x86-64 System V调用约定:在调用C函数时,必须保证被调用者保存寄存器(如rbx)的状态,而原代码直接修改了rbx却没有保存和恢复它。
x86-64 System V调用约定规定:rbx、rbp、r12-r15属于被调用者保存寄存器,也就是说,如果汇编函数要修改这些寄存器,必须先将它们的值压入栈中保存,函数返回前再恢复;而rax、rdi、rsi、rdx等属于调用者保存寄存器,修改它们不需要额外保存。
原代码中,entry函数直接修改了rbx,调用printr(C函数)时,printr会假设rbx的值保持不变,此时rbx已被修改为0,破坏了调用约定,导致后续执行出错。
修改后的tri_tail_rec.s
default rel section .text global entry extern printr ; 将extern声明放在顶部,代码更规范 entry: push rbx ; 进入函数时先保存rbx的原始值 mov rbx, 10 ; 初始化输入值 mov rax, 0 tri: cmp rbx, 0 je done add rax, rbx sub rbx, 1 jmp tri done: call printr pop rbx ; 恢复rbx的原始值 ret
验证编译链接
使用最初的gcc命令即可正常编译运行:
nasm -f elf64 -o tri_tail_rec.o tri_tail_rec.s cc -c -o main.o main.c gcc main.o tri_tail_rec.o -o tri_tail_rec ./tri_tail_rec
运行后会输出:
Printing random stuff.55
(55是10的三角数,计算正确)
关于ld链接的问题
用ld直接链接时出现的_start警告,是因为ld默认寻找_start作为程序入口,而C程序的入口是main,需要通过gcc来链接——gcc会自动添加C运行时库的初始化代码,包括_start入口点。所以建议始终用gcc来链接C和汇编混合的程序,避免手动处理运行时依赖。
内容的提问来源于stack exchange,提问作者bkc4

