函数入参分析
函數入參分析
- 背景
- 固定參數
- 可變參數
- 總結
背景
一般來說,我們調用一個函數的時候,需要傳入對應數據類型和個數的參數,例如調用int sum(int x,int y);我們需要傳入兩個int類型的數,且只能傳入兩個int類型的數。如果類型不匹配或者個數不匹配,編譯器都會報warning或者error。我們將int x ,int y稱作形參,下面就來分析以下參數是怎么傳遞給被調用函數的。
備注:
測試的平臺x86_64-redhat-linux
固定參數
固定參數指的是,一個函數的形參個數是確定的,如上面舉例的int sum(int x,int y); 它的參數個數是2,是一個固定值。還有一種參數個數是可變的,例如printf函數,對于可變參數入參在下一節中分析。
大部分的課本或者圖書上說,函數入參的順序是從右往左依次壓入棧中,由于棧的增長是由高地址向低地址增長,故可以得知越是靠近右邊的參數,所處的地址應該是越大的。例如my_printf(char* fmt,int a,int b,int c),入棧的順序:c->b->a->fmt(指針),地址的大小也是c->b->a->fmt(指針)。
我寫了一個測試程序來驗證這個說法,這個測試程序把函數的入參地址打印出來了:
運行的結果是:
addr: fmt:0x7ffcd7e3d978 a:0x7ffcd7e3d974 b:0x7ffcd7e3d970 c:0x7ffcd7e3d96c從運行的結果看,c的地址最小(0x7ffcd7e3d96c),fmt的地址最大(0x7ffcd7e3d978 ),和大多數的資料上描述不相符。原因實際是:
函數的入參順序和編譯器有關,編譯器決定函數怎么入參,我測試的平臺是x86_64-redhat-linux,大多數資料講的平臺是32位平臺,所以會有差異。
下面通過objdump生成匯編代碼來分析參數入棧的過程:
可以看出,main函數僅僅做了寫入寄存器的操作
my_printf將寄存器中的參數寫入到棧中:
可變參數
按照我的理解,可變參函數入參應該和正常的函數一樣,都會依次將參數壓入到棧中,也會存儲到寄存器中。為了驗證這個想法,所以做了一個實驗:
1)編寫測試代碼
2)gdb加載測試代碼
3)查看棧幀信息,可以看到args中只有num,并沒有看到后面的入參1,2,3
4)查看寄存器,在寄存器中可以看到入參1,2,3
(gdb) info registers rax 0x0 0 rbx 0x0 0 rcx 0x3 3 rdx 0x2 2 rsi 0x1 1 rdi 0x3 3可變參數在gdb上表現不出來,實際也是壓入到棧中了
把棧內存打印出來,棧中有參數,但是參數2,3,4和參數1的位置并不是緊鄰的,從匯編代碼中也可以看出來,以下是棧的內容:
網上的資料和課本上所說的va_list va_arg等,都是在x86平臺上,對于x64平臺上來說,實現完全不一樣。X86平臺的va_list是一個char*類型,但是在我的平臺上,va_list是一個結構體,從gdb可以看出來,p_args是va_list類型的變量,它有gp_offset,fp_offset,overflow_arg_area,reg_save_area成員。所以x86和x64的參數入參是不一樣的,直接套用x86的參數入參會得不到想要的結果:
總結
平臺不同,編譯器不同會導致入參的順序不同,課本和資料上所說的,可能和實際情況不相符,所以只能作為參考,具體情況還需要具體分析。通過分析匯編代碼,會讓你對入參有一個深入的了解。
總結
- 上一篇: java 入参校验_java开发参入参数
- 下一篇: XSS平台 XSS挑战之旅 解题记录