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

Leon3(gr-ut-g99)板卡中程序A调用程序B的实现问题咨询

我之前在Leon3(gr-ut-g99)平台上折腾过跨程序调用的场景,给你整理一套完整可行的方案,从编译配置到加载运行的全流程都讲明白:

完整实现Leon3上程序A调用程序B的方案

一、编译阶段:给程序B固定内存地址并导出关键符号

Leon3是裸机环境,没有操作系统做自动重定位,所以程序B必须被编译到固定的空闲内存地址,这样A才能精准跳转过去。

1. 确定可用内存区域

先通过GRMON2查看板卡的内存映射,找到未被占用的区域:

# 启动GRMON2连接板卡
grmon2 -u gr-ut-g99
# 查看内存信息
grmon> info mem

假设你选定0x400000作为程序B的起始地址(这个地址要避开A的代码、数据栈区域)。

2. 编译程序B并固定地址

用Gaisler Sparc GCC编译时,通过-Ttext参数指定代码段起始地址:

sparc-elf-gcc -mcpu=leon3 -Ttext=0x400000 -o programB.elf programB.c

如果需要让A调用B中的多个函数,而不只是入口,建议给这些函数加上可见性属性导出符号:

// programB.c
#include <stdio.h>

// 导出入口函数,让A可以调用
__attribute__((visibility("default"))) void b_entry() {
    printf("Hello from Program B!\n");
}

// 导出带参数的函数示例
__attribute__((visibility("default"))) int b_calculate(int a, int b) {
    return a * b + 10;
}

编译时加上-fvisibility=hidden,只导出你指定的符号:

sparc-elf-gcc -mcpu=leon3 -Ttext=0x400000 -fvisibility=hidden -o programB.elf programB.c

编译完成后,用sparc-elf-nm查看符号的具体地址,记下来给A用:

sparc-elf-nm programB.elf
# 输出示例:
# 00400120 T b_entry
# 00400150 T b_calculate

二、程序A中实现调用逻辑

在A里可以用两种方式调用B的代码:内联汇编直接跳转,或者函数指针调用(更直观)。

1. 直接跳转到B的入口(无返回/有返回两种情况)

无返回跳转(B执行完直接终止)

如果不需要从B回到A,内联汇编可以这么写:

// programA.c
#include <stdio.h>

#define B_ENTRY_ADDR 0x400000

void call_b_no_return() {
    __asm__ volatile (
        "jmp %0\n"       // 跳转到B的入口地址
        "nop\n"          // Sparc架构的延迟槽,必须跟一条空指令
        :
        : "r"(B_ENTRY_ADDR)
        : "memory"       // 告诉编译器内存会被修改,避免优化导致问题
    );
}

int main() {
    printf("Hello from Program A, calling B...\n");
    call_b_no_return();
    // 下面的代码不会执行,因为B执行完会调用exit
    printf("This line will never run!\n");
    return 0;
}

带返回的跳转(从B回到A继续执行)

如果需要从B返回A,要在跳转时保存A的返回地址,同时B的入口函数要主动返回:

// programA.c中带返回的调用函数
void call_b_and_return() {
    __asm__ volatile (
        "save %sp, -128, %sp\n"  // 建立栈帧,避免B的栈操作破坏A的栈
        "jmp %0\n"
        "mov %%o7, %%l7\n"       // 把A的返回地址保存到局部寄存器l7
        :
        : "r"(B_ENTRY_ADDR)
        : "memory", "l7"
    );
}

同时修改B的入口函数,用ret指令返回:

void b_entry() {
    printf("Hello from Program B!\n");
    ret;  // 返回调用者(A)
}

2. 调用B中的具体函数(带参数和返回值)

如果要调用B中像b_calculate这种带参数的函数,用函数指针比内联汇编更直观:

// programA.c
#include <stdio.h>

#define B_CALC_ADDR 0x400150

// 定义和B中函数匹配的指针类型
typedef int (*b_calc_func)(int, int);

int main() {
    printf("Hello from Program A, calling B's calculate function...\n");
    // 把地址强制转换成函数指针
    b_calc_func calc = (b_calc_func)B_CALC_ADDR;
    int result = calc(6, 8);
    printf("Result from B's calculate: %d\n", result);
    return 0;
}

编译A时不需要特殊地址,直接编译即可:

sparc-elf-gcc -mcpu=leon3 -o programA.elf programA.c

三、GRMON2加载运行流程

现在把两个程序加载到板卡并运行:

  1. 启动GRMON2连接板卡:
grmon2 -u gr-ut-g99
  1. 先加载程序B(因为它的地址是固定的,GRMON2会自动放到指定内存位置):
grmon> load programB.elf

可以用x 0x400000查看内存,确认B已经加载成功。
3. 加载程序A:

grmon> load programA.elf
  1. 运行程序A:
grmon> run

此时就能看到A的输出,以及调用B后的输出(如果是带返回的调用,还会回到A继续执行后续代码)。

四、关键注意事项

  • 内存冲突:必须确保A和B的内存区域完全不重叠,用info mem确认空闲区域。
  • 栈安全:如果B需要使用栈,要在编译B时用-Tdata/-Tbss指定数据段地址,或者自定义链接脚本,避免和A的栈冲突。
  • 寄存器约定:Sparc架构中,l0-l7是被调用者保存寄存器,如果B修改了这些寄存器,要在返回前恢复,否则会破坏A的执行状态。
  • 调试技巧:如果出现问题,用break命令在A的调用处和B的入口处设置断点,逐步排查。

内容的提问来源于stack exchange,提问作者Andak

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.21 06:37:17