1. 概述和問(wèn)題
本文匯編代碼的平臺(tái)及編譯器:arm/gcc。分析函數(shù)調(diào)用棧的規(guī)則對(duì)于理解程序運(yùn)行基本原理很有幫助,匯編代碼分析crash問(wèn)題也大有裨益。本文示例代碼通過(guò)C語(yǔ)言函數(shù)調(diào)用一個(gè)匯編函數(shù),再?gòu)膮R編函數(shù)跳轉(zhuǎn)回C函數(shù),分析該示例的匯編代碼就可以stack frame的創(chuàng)建和arm函數(shù)調(diào)用的傳參規(guī)則。
問(wèn)題:
- arm32使用哪些寄存器傳參,如果參數(shù)超過(guò)4個(gè)怎么傳參?
- arm32/gcc中函數(shù)調(diào)用stack frame的創(chuàng)建,以及函數(shù)返回stack frame的銷毀過(guò)程是怎樣的?
2.arm32函數(shù)傳參規(guī)則和stack frame基本結(jié)構(gòu)
2.1 傳參規(guī)則
- r0-r3傳遞第1-第4個(gè)參數(shù);如果超過(guò)4個(gè)參數(shù)使用棧傳遞參數(shù),且當(dāng)前函數(shù)棧頂(sp指向的地址)放置第5個(gè)參數(shù),sp+4處放置第6個(gè)參數(shù)。
- r0存放返回值
2.2 stack frame基本機(jī)構(gòu)
3.示例代碼
示例代碼包括兩個(gè)源文件:transferParam.c和transferParam.S
transferParam.c:
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
extern void bionic_clone(int flags, int* child_stack, int* parent_tid, int* tls, int* child_tid, int (*fn)(void*), int* arg);
int child(void* v) {
return 0;
}
void my_fork(int flags, int *child_stack, int *ptid, int *tls, int *child_tid, int *child, int *args) {
printf("flags:%d sp:%p ptid:%p tls:%p child_tid:%p child:%p args:%d\n",
flags, child_stack, ptid, tls, child_tid, child, *args);
}
int main(){
int flags = 0;
int *childStack = (int*)0x01;
int *parent_tid = (int*)0x02;
int *tls = (int*)0x03;
int *child_tid = (int*)0x04;
int arg = 5;
printf("%s\n", "before bionic_clone");
bionic_clone(flags, childStack, parent_tid, tls, child_tid, child, &arg);
printf("%s\n", "after bionic_clone");
return 0;
}
c代碼中調(diào)用了匯編函數(shù)bionic_clone,且參數(shù)超過(guò)4個(gè),需要使用棧傳遞參數(shù)。
transferParam.S:
.globl bionic_clone
bionic_clone:
push {fp, lr} @fp, lr入棧
add fp, sp, #4 @fp = sp - 4
@stmfd sp!, {r4, r5, r6}
ldr r4, [fp, #4] @讀取第五個(gè)參數(shù)到r4寄存器
ldr r5, [fp, #8] @讀取第六個(gè)參數(shù)到r5寄存器
ldr r6, [fp, #12] @讀取第7個(gè)參數(shù)到r6寄存器
stmfd sp!, {r4, r5, r6} @r4, r5, r6入棧,以此給my_fork函數(shù)傳參
bl my_fork
sub sp, fp, #4 @sp = fp - 4
pop {fp, pc} @恢復(fù)fp, lr到fp和pc寄存器,實(shí)現(xiàn)函數(shù)返回
.type bionic_clone,%function
代碼執(zhí)行到ldr r6, [fp, #12] stack frame圖示:
?正如main函數(shù)通過(guò)棧給bionic_clone函數(shù)傳遞第5,6,7三個(gè)參數(shù),bionic_clone也將r4 r5 r6入棧給my_fork傳遞參數(shù)
4. arm64棧幀結(jié)構(gòu)
void bar(int a , int b ) {
printf("bar\n");
a = a + b;
printf("%d\n",a);
}
void foo() {
int a = 0;
int b = 1;
bar(a, b);
}
int main(int argc, char *argv[]) {
foo();
}
~
反匯編代碼:
000000000040072c <bar>:
40072c: a9be7bfd stp x29, x30, [sp,#-32]!
400730: 910003fd mov x29, sp
400734: b9001fa0 str w0, [x29,#28]
400738: b9001ba1 str w1, [x29,#24]
40073c: 90000000 adrp x0, 400000 <_init-0x598>
400740: 91216000 add x0, x0, #0x858
400744: 97ffffaf bl 400600 <puts@plt>
400748: b9401fa1 ldr w1, [x29,#28]
40074c: b9401ba0 ldr w0, [x29,#24]
400750: 0b000020 add w0, w1, w0
400754: b9001fa0 str w0, [x29,#28]
400758: 90000000 adrp x0, 400000 <_init-0x598>
40075c: 91218000 add x0, x0, #0x860
400760: b9401fa1 ldr w1, [x29,#28]
400764: 97ffffab bl 400610 <printf@plt>
400768: d503201f nop
40076c: a8c27bfd ldp x29, x30, [sp],#32
400770: d65f03c0 ret
0000000000400774 <foo>:
400774: a9be7bfd stp x29, x30, [sp,#-32]!
400778: 910003fd mov x29, sp
40077c: b9001fbf str wzr, [x29,#28]
400780: 52800020 mov w0, #0x1 // #1
400784: b9001ba0 str w0, [x29,#24]
400788: b9401ba1 ldr w1, [x29,#24]
40078c: b9401fa0 ldr w0, [x29,#28]
400790: 97ffffe7 bl 40072c <bar>
400794: d503201f nop
400798: a8c27bfd ldp x29, x30, [sp],#32
40079c: d65f03c0 ret
00000000004007a0 <main>:
4007a0: a9be7bfd stp x29, x30, [sp,#-32]!
4007a4: 910003fd mov x29, sp
4007a8: b9001fa0 str w0, [x29,#28]
4007ac: f9000ba1 str x1, [x29,#16]
4007b0: 97fffff1 bl 400774 <foo>
4007b4: 52800000 mov w0, #0x0 // #0
4007b8: a8c27bfd ldp x29, x30, [sp],#32
4007bc: d65f03c0 ret
arm64棧幀結(jié)構(gòu):
?5. 實(shí)戰(zhàn),內(nèi)核如何dump bactrace
為了加深stack frame的理解,可以分析arm64如何dump bactrace。內(nèi)核配置CONFIG_FRAME_POINTER可以基于fp?;厮荨;驹砜梢钥礂Y(jié)構(gòu)中,比如arm64小節(jié)示例代碼中,main調(diào)用foo,foo調(diào)用bar,我們從bar開始回溯棧幀,如果我們先得到bar的x29值,那么從x29 + 8處保存了x30,即為caller調(diào)用者的地址,bar x29又可以回溯到foo函數(shù)的棧幀結(jié)構(gòu),依次類推就可以回溯整個(gè)函數(shù)調(diào)用。文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-677860.html
kernel-4.14/arch/arm64/kernel/traps.c:文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-677860.html
void dump_backtrace(struct pt_regs *regs, struct task_struct *tsk)
{
struct stackframe frame;
int skip;
pr_debug("%s(regs = %p tsk = %p)\n", __func__, regs, tsk);
if (!tsk)
tsk = current;
if (!try_get_task_stack(tsk))
return;
//假設(shè)是dump當(dāng)前task的backtrace
if (tsk == current) {
//__builtin_frame_address是編譯內(nèi)置函數(shù),返回當(dāng)前棧棧幀地址即x29.
frame.fp = (unsigned long)__builtin_frame_address(0);
frame.pc = (unsigned long)dump_backtrace;
} else {
/*
* task blocked in __switch_to
*/
frame.fp = thread_saved_fp(tsk);
frame.pc = thread_saved_pc(tsk);
}
skip = !!regs;
printk("Call trace:\n");
while (1) {
unsigned long stack;
int ret;
//dump_backtrace_entry打印frame.pc的值
/* skip until specified stack frame */
if (!skip) {
dump_backtrace_entry(frame.pc);
} else if (frame.fp == regs->regs[29]) {
skip = 0;
/*
* Mostly, this is the case where this function is
* called in panic/abort. As exception handler's
* stack frame does not contain the corresponding pc
* at which an exception has taken place, use regs->pc
* instead.
*/
dump_backtrace_entry(regs->pc);
}
ret = unwind_frame(tsk, &frame);
if (ret < 0)
break;
if (in_entry_text(frame.pc)) {
stack = frame.fp - offsetof(struct pt_regs, stackframe);
if (on_accessible_stack(tsk, stack))
dump_mem("", "Exception stack", stack,
stack + sizeof(struct pt_regs));
}
}
put_task_stack(tsk);
}
/*
* AArch64 PCS assigns the frame pointer to x29.
*
* A simple function prologue looks like this:
* sub sp, sp, #0x10
* stp x29, x30, [sp]
* mov x29, sp
*
* A simple function epilogue looks like this:
* mov sp, x29
* ldp x29, x30, [sp]
* add sp, sp, #0x10
*/
int notrace unwind_frame(struct task_struct *tsk, struct stackframe *frame)
{
unsigned long fp = frame->fp;
if (fp & 0xf)
return -EINVAL;
if (!tsk)
tsk = current;
if (!on_accessible_stack(tsk, fp))
return -EINVAL;
//獲取上一級(jí)(caller)的fp值,具體可以看arm64棧幀結(jié)構(gòu)
frame->fp = READ_ONCE_NOCHECK(*(unsigned long *)(fp));
//fp+8存儲(chǔ)的是caller調(diào)用之的地址(即返回地址),具體可以對(duì)著arm64棧幀結(jié)構(gòu)看
frame->pc = READ_ONCE_NOCHECK(*(unsigned long *)(fp + 8));
/*
* Frames created upon entry from EL0 have NULL FP and PC values, so
* don't bother reporting these. Frames created by __noreturn functions
* might have a valid FP even if PC is bogus, so only terminate where
* both are NULL.
*/
if (!frame->fp && !frame->pc)
return -EINVAL;
return 0;
}
到了這里,關(guān)于arm/arm64函數(shù)棧幀(stackframe)結(jié)構(gòu)和傳參規(guī)則的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!