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加载运行流程
现在把两个程序加载到板卡并运行:
- 启动GRMON2连接板卡:
grmon2 -u gr-ut-g99
- 先加载程序B(因为它的地址是固定的,GRMON2会自动放到指定内存位置):
grmon> load programB.elf
可以用x 0x400000查看内存,确认B已经加载成功。
3. 加载程序A:
grmon> load programA.elf
- 运行程序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
相关产品推荐
相关产品推荐

