学习 SPARC 汇编中函数调用的基本示例
Posted
技术标签:
【中文标题】学习 SPARC 汇编中函数调用的基本示例【英文标题】:Learning about basic example of function call in SPARC assembly 【发布时间】:2014-12-06 23:53:28 【问题描述】:我正在通过一个简单的示例学习 SPARC 汇编,您可以在下面看到。我有几个关于这个例子的问题,它显示了一个过程的传递参数。
在主要部分,我将5
设置为第一个输入参数%o0
,将7
设置为第二个输入参数%o1
。之后,我将这些寄存器的总和放入%o2
。然后,我调用打印这个总和的“测试”函数。
fmt0:
.asciz "%d\n"
.align 4
.global main
main:
save %sp, -64, %sp
mov 5, %o0
mov 7, %o1
add %o0, %o1, %o2
!st %o2, [%fp-4] for storing %o2 at adress %fp-4
call test
nop
ret
test:
mov %i2, %o1
!ld [%fp-4], %o1 for loading from %fp-4 into %o1
set fmt0, %o0
call printf
nop
ret
使用上面的代码,它会打印值“-273929364”而不是“12”(使用 gcc 编译)。
“mov %i2, %o1
”似乎不起作用。我知道主部分中的%o2
寄存器在被调用过程中变为%i2
,但是为什么我不能使用此“mov
”指令将%i2
的值直接设置为%o1
寄存器?
第二个问题:如果我取消注释主要部分中的说明“st %o2, [%fp-4]
”和测试功能中的“ld [%fp-4], %o1
”并注释“mov %i2, %o1
”,它会正确打印“12”。我们如何知道正确的偏移量作为传递参数的函数?
据我所知,%sp
在“save %sp, -64, %sp
”指令之后变成了%fp
? %fp
在 main section
和 test function
中有相同的值吗?
最后,我在不同的示例中看到了指令“call function, 0
”或“call printf, 0
”:为什么我必须在调用的函数名称后添加一个“0”?这是返回值吗(如int main(void) ... return 0;
)?
感谢您的帮助
【问题讨论】:
【参考方案1】:我知道主要部分中的 %o2 寄存器在被调用时变为 %i2 程序,但为什么我不能直接将 %i2 的值设置为 %o1 用这个“mov”指令注册?
%o
寄存器仅在执行save
后变为%i
,通常在被调用函数的开头。在您的示例中,test
函数没有 save
/restore
。
旋转寄存器窗口的是save
/restore
,而不是call
/ret
。由于test
不是叶函数(它从内部调用printf
),它必须有自己的寄存器窗口。所以你必须用save
/restore
包装test
函数:
test:
save %sp, -64, %sp
mov %i2, %o1
set fmt0, %o0
call printf
nop
ret
restore
否则,您的参数仍然可以通过%i2
获得,但无论如何代码都是错误的,因为call printf
指令会破坏存储在%o7
中的test
返回地址。
更新。
关于编辑建议中的问题(顺便说一句,不要这样做,而是在 cmets 中提问):
如果 %o7 在非叶过程中被覆盖,如何规避这个问题 问题?我认为我必须在开头推 %o7 另一个寄存器中的非叶过程并在最后弹出它,即 嵌套过程调用后,是不是?
在非叶子程序的情况下没有问题:save
/restore
解决问题。您可能将save
视为“批量”推送:它为您提供了一个新的寄存器窗口——一组16 个寄存器(8 个%i
+ 8 个%l
)在嵌套过程调用中保留它们的值。相应地,restore
会将您带回到之前保存的窗口。
所有%o
寄存器都可以通过%i
在新窗口中访问。也就是说,调用者将参数设置为%o0 .. %o5
(最多6 个,因为%o6
和%o7
是为堆栈指针和返回地址保留的)。被调用者生成save
并从%i0 .. %i5
获取参数。如果它想要返回一个值,它会将其放入%i0
。返回时会生成restore
,调用者可以在%o0
中看到返回值(如果有)。
这也回答了你的另一个问题:
据我所知,%sp 在 "save %sp, -64, %sp" 之后变成 %fp 操作说明?在主要部分和测试中具有相同的 %fp 值 功能?
%sp
只是%o6
的别名,%fp
是%i6
的别名。除了旋转窗口,save
还可以像序号add
指令一样添加值。 save %sp, -64, %sp
的含义如下:取旧窗口的 %sp
值,旋转窗口(@987654367@ 变为 %fp
),将 -64 添加到该值并将结果放入 的 %sp
新的窗口。
换句话说,
save %sp, -64, %sp
做同样的事情
save
add %fp, -64, %sp ! notice that the source register is now %fp, not %sp
顺便说一句,-64 只是寄存器窗口的大小(16 个寄存器,每个 4 个字节)。它是负数,因为堆栈向下增长。
这是一个excellent answer 解释 SPARC 寄存器窗口的概念。
更新。 2
刚刚注意到您的“寻找来自可靠和/或官方来源的答案”声明。 SPARC v8 architecture manual 是必读的,尤其是涉及延迟槽、寄存器窗口、叶过程优化和软件注意事项的章节。
【讨论】:
restore
不应该出现在ret
之前吗?
在我的代码中restore
在ret
的延迟槽中执行(注意一个空格缩进)。但是,您可以先执行restore
,但在这种情况下,您应该使用retl
(从%o7
获取返回地址),而不是ret
(%i7
)。
@EldarAbusalimov 我以前没有见过单空格缩进。只是为了澄清一下,那个缩进表示该指令是在前一条指令的延迟槽中执行的?
@Jonathon 是的,你是对的。这只是 SPARC 组装的协议 (AFAIK)。我在一些项目中看到过它,例如在 Linux 内核中。唯一的目的是提高代码的可读性。
@EldarAbusalimov 我不明白为什么我们应该把retl
放在restore
之后。实际上,在这种情况下,恢复是在retl
之前执行的(在延迟槽中),所以%o7
在这两种情况下都变成%i7
(restore
在ret
之后和ret
在restore
之后)以上是关于学习 SPARC 汇编中函数调用的基本示例的主要内容,如果未能解决你的问题,请参考以下文章
逆向——C语言的汇编表示之堆栈图 手把手示例 可以见后面在函数内部加一个局部变量以及嵌套调用的例子来综合理解