函数使用了堆栈的字节超过_在C语言中如何访问堆栈?
堆棧一般是用來保存變量之類的東西(靜態變量在內存中,雖然堆棧就是內存的一部分,但為了防止歧義,還是分成兩部分來說),一般情況下沒必要去故意讀取堆棧的值,變量用變量名就可以直接訪問,但我曾經想要讀取函數返回后代碼繼續執行的地址,因此想到了來讀取堆棧(函數調用時,會向堆棧中壓入參數和下一個代碼執行的地址,這樣就可以在函數返回后繼續執行)。
先來測試一下我們能否讀取堆棧:
#include<stdio.h> int main() {volatile int a=24;/*設置一個我們要讀取的變量,volatile 可以告訴gcc不要優化這行代碼,僅對變量有效*/volatile int b[2]={1,2};/*建立一個數組,這個數組是關鍵,這時b作為數組指針,指向第一個元素,即 1在堆棧中的儲存位置,因此我們就可以利用b來讀取堆棧的任意位置(該程序所擁有的堆棧)*/volatile int c=b[2];printf("%dn",c);//打印出指定位置堆棧的值 return 0; }當然,如果不設定編譯器的參數,這樣的代碼可能是不會編譯通過的(注意:可能),命令如下:
gcc -Wno-unused -m32 -S -O0 -o test.s test.c
源文件名為test.c,參數說明:
-Wno-unused:不警告未使用的變量(上面的程序不需要,但為了方便自己分析,放在這里)
-m32:編譯為32位程序
-S:編譯為匯編文件
-O0:優化等級為0
-o:重命名輸出文件
現在讓我們看看匯編文件是什么樣的:
.file "test.c".def ___main; .scl 2; .type 32; .endef.section .rdata,"dr" LC0:.ascii "%d120".text.globl _main.def _main; .scl 2; .type 32; .endef _main: LFB10:.cfi_startprocpushl %ebp.cfi_def_cfa_offset 8.cfi_offset 5, -8movl %esp, %ebp.cfi_def_cfa_register 5pushl %esipushl %ebxandl $-16, %espsubl $32, %esp.cfi_offset 6, -12.cfi_offset 3, -16call ___mainmovl $24, 28(%esp) //將24存入堆棧,位置是28+esp的值movl $1, %ebx //1存入ebxmovl $2, %esi //2存入esimovl %ebx, 20(%esp) //1存入20(%esp)movl %esi, 24(%esp) //2存入24(%esp)movl 28(%esp), %eax //將28(%esp)的值存入eax,這里對應的代碼就是c=b[2],即將24存入了eaxmovl %eax, 16(%esp) //剩下的就是將參數壓入堆棧,然后調用printf,這里不再解釋movl 16(%esp), %eaxmovl %eax, 4(%esp)movl $LC0, (%esp)call _printfmovl $0, %eaxleal -8(%ebp), %esppopl %ebx.cfi_restore 3popl %esi.cfi_restore 6popl %ebp.cfi_restore 5.cfi_def_cfa 4, 4ret.cfi_endproc LFE10:.ident "GCC: (MinGW.org GCC-6.3.0-1) 6.3.0".def _printf; .scl 2; .type 32; .endef通過匯編的內容,我們可以看出可以使用一個數組來訪問有效堆棧內的全部內容(超出堆棧界限會引發錯誤)。輸出結果如下:
當調用一個函數時(使用call指令),壓入參數的同時會壓下一個代碼的地址,使函數返回后可以繼續執行。現在來嘗試獲取這個地址,代碼如下:
#include<stdio.h> void fun() {volatile int a[1];/*設置一個數組,使用這個數組來訪問堆棧*/a[0]=14;printf("a[4]=%dn",a[4]);/*打印出call指令壓入的地址,這里很有意思,我之前以為這個地址在 a[2],a[0]=14,a[1]是esp的值(C語言中所有函數的開頭都會有push ebp的代碼,將ebp的值保存進堆棧,然后 將esp保存進ebp),但實際上發現總會有兩個不知名的值占據著a[2],a[3]的位置,具體可以參見匯編代碼*/goto *(a[4]);/*使用goto語句可以讓程序跳向任何合法地址,goto不僅可以用標號或者行號,還可以是任 何void*型的變量(前提是程序可以訪問該地址),goto會被程序翻譯為jmp指令,而(*(void(*) (void))0x100000)();這樣的跳轉方式將會被翻譯為call指令,會使堆棧中多出一個地址,具體要使用哪個需要 參考實際。*/ } int main() { fun(); printf("hello");/*理論上如果上面的goto生效,那么hello將會被執行兩次(調用fun函數時,堆棧被壓入該 地址,然后使用了goto后,跳轉到這里執行一次,打印出一個hello,在下面的return 0;語句中,程序會認為當 前還在fun函數,畢竟堆棧中的地址還沒有釋放,因此重新返回到這里,再執行一次),否則由于地址錯誤,程 序將被迫退出,不會在控制臺看到hello*/ return 0; }下面是實際執行的情況,可見我們確實得到了之前壓入的那個地址:
以下是上面的那個程序的匯編程序:
.file "test.c".section .rdata,"dr" LC0:.ascii "a[4]=%d120".text.globl _fun.def _fun; .scl 2; .type 32; .endef _fun: LFB10:.cfi_startprocpushl %ebp //將ebp的值送入堆棧.cfi_def_cfa_offset 8.cfi_offset 5, -8movl %esp, %ebp.cfi_def_cfa_register 5subl $40, %esp //空出40字節的位置用來儲存變量movl $14, -12(%ebp) //14存入a[0],所以a的值即是ebp偏移12個字節, //可以推斷出a[1]在-8(%ebp),a[2]在-4(%ebp),a[3]在0(%esp),所以a[3]是之前保存的ebp的值 //那么a[4]就是call指令保存的值,這里比較令人好奇為什么a[0]在-12(%ebp)movl 4(%ebp), %eax movl %eax, 4(%esp)movl $LC0, (%esp)call _printfmovl 4(%ebp), %eaxjmp *%eax //goto被翻譯為jmp指令,然后跳向了我們指定的地址.cfi_endproc LFE10:.def ___main; .scl 2; .type 32; .endef.section .rdata,"dr" LC1:.ascii "hello0".text.globl _main.def _main; .scl 2; .type 32; .endef _main: LFB11:.cfi_startprocpushl %ebp.cfi_def_cfa_offset 8.cfi_offset 5, -8movl %esp, %ebp.cfi_def_cfa_register 5andl $-16, %espsubl $16, %espcall ___maincall _funmovl $LC1, (%esp)call _printfmovl $0, %eaxleave.cfi_restore 5.cfi_def_cfa 4, 4ret.cfi_endproc LFE11:.ident "GCC: (MinGW.org GCC-6.3.0-1) 6.3.0".def _printf; .scl 2; .type 32; .endef所以這里驗證了我們可以通過操作數組來讀取堆棧。
創作挑戰賽新人創作獎勵來咯,堅持創作打卡瓜分現金大獎總結
以上是生活随笔為你收集整理的函数使用了堆栈的字节超过_在C语言中如何访问堆栈?的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 鸿运中短债基金A和C收益怎么算?附计算案
- 下一篇: 2016白户申卡最实用的方法:掌握四招申