ARM汇编向C 函数传递参数
我在从内联汇编调用C 函数时遇到了一个问题。我得说我尝试了来自另一个话题的一个解决方案。
我在Android上工作。用Cxxdroid编译(没问题)。
下面是代码。
#include <stdio.h>
struct iphdr {
uint8_t hdrlen:4;
uint8_t version:4;
uint8_t ecn:2; // Explicit Congestion Notification - RFC 3168
uint8_t dscp:6; // DiffServ Code Point
uint16_t length;
uint16_t ident;
uint16_t fragoff:13;
uint16_t flags:3;
uint8_t ttl;
uint8_t protocol;
uint16_t checksum;
uint32_t srcip;
uint32_t dstip;
uint32_t options[ ]; // Present if hdrlen > 5
} __attribute__((__packed__));
int _print_screen(void)
{ printf("Hello");
//eventually print r0 -r3
}
int sum(int k, int l, int m, uint8_t n, uint32_t o, uint32_t p)
{
int res =0;
__asm ("sum:\n"
"CMP %[input_i], #0\n"
"BEQ endrec\n"
"endrec:\n"
"SUB %[input_i], %[input_i], #1\n"
"BL %[p_screen]\n"
:[result] "+r" (res)
:[input_i] "r" (k), [input_j] "r" (l), [input_k] "r" (m), [input_l] "r"(n), [input_m] "r" (o), [input_n] "r" (p), [p_screen] "r" (_print_screen)
: "r0" //clobber
);
return res;
}
int main(void)
{
iphdr iph;
iph.version='\x4';
iph.hdrlen='\x5';
//iph.ip_tos='\x00';
iph.protocol='\x03';
iph.srcip='1.1.1.2';
iph.dstip='1.1.1.1';
d=1;
e = sum(d, iph.version, iph.hdrlen, iph.protocol, iph.srcip, iph.dstip);
}
error: expected label or encodable integer pc offset "BL %[p_screen]\n"
我还无法让汇编调用我的C 函数。此外我也希望在C 函数中读取r0到 r3。
解决方案
你目前正在尝试做的是通过寄存器传递函数的地址([p_screen] "r" (_print_screen))。这需要你使用 BLX 进行间接调用,示例如下:
"BLX %[p_screen]\n"
然而这既低效又在此处并非必要,所以最好直接通过 BL 调用函数,而不使用寄存器。真正的问题是,直接方法为什么不起作用?
"BL _print_screen\n"
答案是,CxxDroid实际上默认编译的是 C++ 代码,而不是C。
在C++中,函数名的链接符号会被进行名称混淆(mangling,用于支持重载、模板等),因此你不能直接按名称调用它们。例如 _print_screen 函数的符号可能会被混淆为 _Z13_print_screenv——不过这取决于平台/编译器。解决办法是在函数上添加 extern "C",使其禁用混淆,导致链接符号为 _print_screen。这样你也不用通过 [p_screen] "r" (_print_screen) 把函数地址放入寄存器:
extern "C"
int _print_screen(void)
{ ... }
int sum(int k, int l, int m, uint8_t n, uint32_t o, uint32_t p)
{
__asm ("BL _print_screen\n");
}
另外,你也可以通过设置菜单把CxxDroid切换到C 模式。
你也可以把 extern "C" 部分包裹在 #ifdef __cplusplus 里,这样它只在你编译为C++代码时生效,而在C 时不生效:
#ifdef __cplusplus
extern "C"
#endif
int _print_screen(void)
{ ... }
除了前面提到的关于多字符常量 '1.1.1.2' 的方面(注意那些编译器警告!),代码还有一些其他问题。
不要把函数的前序/后序写入内联汇编中,也不要使用标签,例如代码中的 "sum:\n"。
- 在C++中这看起来似乎“有效”,因为C++函数的实际名称
sum会被混淆,即你会得到一个符号sum,以及一个类似的符号_Z3sumiiihjj,很可能会造成问题 - 在C 中你会得到错误,因为C 函数
sum保留了自己的名称,你将有两个符号定义sum。 - 对于内联汇编代码中的跳转,使用 本地标签。
完整示例(C和 C++):
#ifdef __cplusplus
extern "C"
#endif
int _print_screen(void)
{ printf("Hello");
//eventually print r0 -r3
}
int sum(int k, int l, int m, uint8_t n, uint32_t o, uint32_t p)
{
int res =0;
__asm (
"CMP %[input_i], #0\n"
"BEQ 1f\n"
"1:\n"
"SUB %[input_i], %[input_i], #1\n"
"BL _print_screen\n"
:[result] "+r" (res)
:[input_i] "r" (k), [input_j] "r" (l), [input_k] "r" (m), [input_l] "r"(n), [input_m] "r" (o), [input_n] "r" (p)
: "r0" //clobber
);
return res;
}
内联汇编确实可以非常棘手,尤其是在启用优化时。如果你的整个函数体都是汇编代码,直接把整个函数放到一个单独的汇编文件中,而不使用内联汇编,通常会简单得多。