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

Linux x86-64下C与NASM双向调用链接失败求助

问题: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

尝试的编译链接命令

  1. 使用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

运行后出现段错误。

  1. 使用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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.05 18:06:07