ARM汇编中的函数定义并不像高级语言那样有特定的语法,但通常可以通过 标签(label) 和 子程序调用指令 (如BL,BLX) 来实现类似于函数的功能。
例如,下面的代码定义了一个名为 my_function
的 “函数”,它接受一个参数(通过寄存器r0
传递),将其值增加 1
后返回:
my_function:
add r0, r0, #1
bx lr
在这里,my_function
是一个标签,表示这个函数的入口点。add r0, r0, #1
是函数的主体,它将寄存器 r0
的值增加 1
。最后,bx lr
是函数的退出语句,它将执行权返回给调用者。
这个函数可以通过 BL
指令来调用,例如:
mov r0, #5
bl my_function
在这里,mov r0, #5
将值5
加载到寄存器r0
中,然后bl my_function
跳转到my_function
标签处执行代码,同时将返回地址(即下一条指令的地址)保存到链接寄存器lr
中。
需要注意的是,这只是最基本的情况。在实际的ARM汇编代码中,函数可能会更复杂,包括更多的寄存器操作、保存和恢复现场、处理函数调用堆栈等。
在ARM汇编中,标签(Label)是用来标记代码中某一位置的一种机制。标签后面通常跟着一个冒号(:),然后在其后可以是一条或多条汇编指令。我们可以使用标签来作为跳转指令(如b,beq等)的目标,或者作为数据的引用。
例如下面的代码:
start:
mov r0, #10
add r1, r0, #5
loop:
subs r0, r0, #1
bne loop
在这个代码中,"start
"和"loop
"就是两个标签。我们可以看到,在"loop
"标签后面,有一个循环,它会一直执行,直到r0
的值为0
为止。
注意,不能在两个不同的地方定义相同名字的标签。而且,标签名是大小写敏感的,也就是说,"Loop
"和"loop
"是两个不同的标签。
在 ARM 汇编中,“.global
”,“.section
”,".type
"都是汇编器指令,它们用于指示汇编器如何处理随后的汇编代码。
“.global”:它用于声明一个全局标签,也就是说这个标签可以在其他的汇编文件中引用。例如,".global my_func
"声明了一个名为my_func
的全局标签。
“.section”:它用于指定接下来的代码或数据应该放在哪个段(section)中。例如,“.section .text
"指定接下来的代码应该放在名为".text
"的段中。在链接过程中,链接器会将同名的段合并在一起。在”.section
"指令后面通常会跟着一个段名,以及一些可选的段属性,如 ,“ax” 段属性,表示这个段是可以执行的(‘x
’)和可以读写的(‘a
’)。
“.type”:它用于指定一个符号(通常是一个标签)的类型,这对链接器解析符号的方式有所影响。例如,“.type my_func, %function
"指定 my_func
是一个函数类型的符号。”%function
"是 GNU汇编器的一个预定义符号类型,表示这个符号是一个函数。这对链接器以及某些调试工具是有用的,它们可以通过这个类型信息来正确地处理这个符号
以下是一个简单的函数定义的例子:
.global my_func
.type my_func, %function
.section .text
my_func:
mov r0, #1
bx lr
在这个例子中,我们定义了一个名为 my_func
的全局函数,这个函数在.text
段中,函数的功能是将1
放入r0
寄存器,然后返回。
见:edk2/ArmPkg/Include/AsmMacroIoLibV8.h
#define _ASM_FUNC(Name, Section) \
.global Name ; \
.section #Section, "ax" ; \
.type Name, %function ; \
Name:
#define ASM_FUNC(Name) _ASM_FUNC(ASM_PFX(Name), .text. ## Name)
#Section
中的 #
的作用是讲将 Section
字符串化,就是将 _ASM_FUNC
中的 .text
转换成字符串;
//x0 postcode value
ASM_FUNC (PostCode_S)
mov x24, x1
mov x1, #0x87000000
str x0,[x1]
mov x1, x24
ret
关于宏 ASM_PFX
的定义如下(edk2/MdePkg/Include/Base.h
):
//
// For symbol name in assembly code, an extra "_" is sometimes necessary
//
///
/// Private worker functions for ASM_PFX()
///
#define _CONCATENATE(a, b) __CONCATENATE(a, b)
#define __CONCATENATE(a, b) a ## b
///
/// The __USER_LABEL_PREFIX__ macro predefined by GNUC represents the prefix
/// on symbols in assembly language.
///
#define ASM_PFX(name) _CONCATENATE (__USER_LABEL_PREFIX__, name)
从上面的定义可以看出最后是给 ASM_PFX(name)
中的 name
加了一个 __USER_LABEL_PREFIX__
前缀。
在 GNU C中,“__USER_LABEL_PREFIX__
” 是一个预定义宏,它的值代表了在该编译环境中用户定义的标签前缀。
在某些平台和编译器中,用户定义的函数和变量在汇编层面需要添加一个前缀。例如,在许多 UNIX 系统中,用户定义的标签需要添加一个下划线"_
"作为前缀。
例如,如果你在 C 代码中定义了一个函数"my_function
",那么在生成的汇编代码中,这个函数的名称会变成"_my_function
"。
在这种情况下,“__USER_LABEL_PREFIX__
“的值就会被定义为”_
”。然而,在不需要添加前缀的环境中,"__USER_LABEL_PREFIX__
"的值就会被定义为空。
这个宏在处理跨平台代码时很有用,特别是需要直接编写汇编代码时,可以通过这个宏来正确地引用C函数或变量,避免平台差异带来的问题。
例如:
void my_function(void);
asm(".global " __user_label_prefix__ "my_function");
这段代码在需要添加前缀的环境中会被扩展为".global _my_function
",而在不需要添加前缀的环境中会被扩展为".global my_function
"。
在 UEFI 的代码edk2/MdePkg/Include/AArch64/ProcessorBind.h
中可以看到 __USER_LABEL_PREFIX__
定义为空:
#ifndef __USER_LABEL_PREFIX__
#define __USER_LABEL_PREFIX__
#endif
上篇文章:ARM64 常见汇编指令学习 11 – ARM 汇编宏 .macro 的学习