日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

函数使用了堆栈的字节超过_在C语言中如何访问堆栈?

發(fā)布時(shí)間:2023/12/10 编程问答 34 豆豆
生活随笔 收集整理的這篇文章主要介紹了 函数使用了堆栈的字节超过_在C语言中如何访问堆栈? 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

堆棧一般是用來保存變量之類的東西(靜態(tài)變量在內(nèi)存中,雖然堆棧就是內(nèi)存的一部分,但為了防止歧義,還是分成兩部分來說),一般情況下沒必要去故意讀取堆棧的值,變量用變量名就可以直接訪問,但我曾經(jīng)想要讀取函數(shù)返回后代碼繼續(xù)執(zhí)行的地址,因此想到了來讀取堆棧(函數(shù)調(diào)用時(shí),會向堆棧中壓入?yún)?shù)和下一個(gè)代碼執(zhí)行的地址,這樣就可以在函數(shù)返回后繼續(xù)執(zhí)行)。

先來測試一下我們能否讀取堆棧:

#include<stdio.h> int main() {volatile int a=24;/*設(shè)置一個(gè)我們要讀取的變量,volatile 可以告訴gcc不要優(yōu)化這行代碼,僅對變量有效*/volatile int b[2]={1,2};/*建立一個(gè)數(shù)組,這個(gè)數(shù)組是關(guān)鍵,這時(shí)b作為數(shù)組指針,指向第一個(gè)元素,即 1在堆棧中的儲存位置,因此我們就可以利用b來讀取堆棧的任意位置(該程序所擁有的堆棧)*/volatile int c=b[2];printf("%dn",c);//打印出指定位置堆棧的值 return 0; }

當(dāng)然,如果不設(shè)定編譯器的參數(shù),這樣的代碼可能是不會編譯通過的(注意:可能),命令如下:

gcc -Wno-unused -m32 -S -O0 -o test.s test.c

源文件名為test.c,參數(shù)說明:

-Wno-unused:不警告未使用的變量(上面的程序不需要,但為了方便自己分析,放在這里)

-m32:編譯為32位程序

-S:編譯為匯編文件

-O0:優(yōu)化等級為0

-o:重命名輸出文件

現(xiàn)在讓我們看看匯編文件是什么樣的:

.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,這里對應(yīng)的代碼就是c=b[2],即將24存入了eaxmovl %eax, 16(%esp) //剩下的就是將參數(shù)壓入堆棧,然后調(diào)用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

通過匯編的內(nèi)容,我們可以看出可以使用一個(gè)數(shù)組來訪問有效堆棧內(nèi)的全部內(nèi)容(超出堆棧界限會引發(fā)錯(cuò)誤)。輸出結(jié)果如下:

當(dāng)調(diào)用一個(gè)函數(shù)時(shí)(使用call指令),壓入?yún)?shù)的同時(shí)會壓下一個(gè)代碼的地址,使函數(shù)返回后可以繼續(xù)執(zhí)行。現(xiàn)在來嘗試獲取這個(gè)地址,代碼如下:

#include<stdio.h> void fun() {volatile int a[1];/*設(shè)置一個(gè)數(shù)組,使用這個(gè)數(shù)組來訪問堆棧*/a[0]=14;printf("a[4]=%dn",a[4]);/*打印出call指令壓入的地址,這里很有意思,我之前以為這個(gè)地址在 a[2],a[0]=14,a[1]是esp的值(C語言中所有函數(shù)的開頭都會有push ebp的代碼,將ebp的值保存進(jìn)堆棧,然后 將esp保存進(jìn)ebp),但實(shí)際上發(fā)現(xiàn)總會有兩個(gè)不知名的值占據(jù)著a[2],a[3]的位置,具體可以參見匯編代碼*/goto *(a[4]);/*使用goto語句可以讓程序跳向任何合法地址,goto不僅可以用標(biāo)號或者行號,還可以是任 何void*型的變量(前提是程序可以訪問該地址),goto會被程序翻譯為jmp指令,而(*(void(*) (void))0x100000)();這樣的跳轉(zhuǎn)方式將會被翻譯為call指令,會使堆棧中多出一個(gè)地址,具體要使用哪個(gè)需要 參考實(shí)際。*/ } int main() { fun(); printf("hello");/*理論上如果上面的goto生效,那么hello將會被執(zhí)行兩次(調(diào)用fun函數(shù)時(shí),堆棧被壓入該 地址,然后使用了goto后,跳轉(zhuǎn)到這里執(zhí)行一次,打印出一個(gè)hello,在下面的return 0;語句中,程序會認(rèn)為當(dāng) 前還在fun函數(shù),畢竟堆棧中的地址還沒有釋放,因此重新返回到這里,再執(zhí)行一次),否則由于地址錯(cuò)誤,程 序?qū)⒈黄韧顺?#xff0c;不會在控制臺看到hello*/ return 0; }

下面是實(shí)際執(zhí)行的情況,可見我們確實(shí)得到了之前壓入的那個(gè)地址:

以下是上面的那個(gè)程序的匯編程序:

.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字節(jié)的位置用來儲存變量movl $14, -12(%ebp) //14存入a[0],所以a的值即是ebp偏移12個(gè)字節(jié), //可以推斷出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

所以這里驗(yàn)證了我們可以通過操作數(shù)組來讀取堆棧。

創(chuàng)作挑戰(zhàn)賽新人創(chuàng)作獎(jiǎng)勵(lì)來咯,堅(jiān)持創(chuàng)作打卡瓜分現(xiàn)金大獎(jiǎng)

總結(jié)

以上是生活随笔為你收集整理的函数使用了堆栈的字节超过_在C语言中如何访问堆栈?的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。