Linux-Kernel

ELF 載入器如何確定初始堆棧大小?

  • May 31, 2019

我正在研究 ELF 規範(http://www.skyfree.org/linux/references/ELF_Format.pdf),關於程序載入過程我不清楚的一點是堆棧是如何初始化的,以及初始頁面大小是。這是測試(在 Ubuntu x86-64 上):

$ cat test.s
.text
 .global _start
_start:
 mov $0x3c,%eax
 mov $0,%edi
 syscall
$ as test.s -o test.o && ld test.o
$ gdb a.out -q
Reading symbols from a.out...(no debugging symbols found)...done.
(gdb) b _start
Breakpoint 1 at 0x400078
(gdb) run
Starting program: ~/a.out 

Breakpoint 1, 0x0000000000400078 in _start ()
(gdb) print $sp
$1 = (void *) 0x7fffffffdf00
(gdb) info proc map
process 20062
Mapped address spaces:

         Start Addr           End Addr       Size     Offset objfile
           0x400000           0x401000     0x1000        0x0 ~/a.out
     0x7ffff7ffa000     0x7ffff7ffd000     0x3000        0x0 [vvar]
     0x7ffff7ffd000     0x7ffff7fff000     0x2000        0x0 [vdso]
     0x7ffffffde000     0x7ffffffff000    0x21000        0x0 [stack]
 0xffffffffff600000 0xffffffffff601000     0x1000        0x0 [vsyscall]

ELF 規範對這個堆棧頁面如何或為什麼首先存在幾乎沒有說明,但我可以找到說明堆棧應該初始化為 SP 指向 argc,argv、envp 和輔助向量就在上面我已經證實了這一點。但是在 SP 之下還有多少可用空間呢?在我的系統上,0x1FF00在 SP 下方映射了字節,但大概是從堆棧頂部的 處倒數,並且在完整映射中0x7ffffffff000有字節。0x21000什麼影響這個數字?

我知道堆棧下方的頁面是一個“保護頁面”,如果我寫入它,它會自動變為可寫並“在堆棧中向下增長”(大概是這樣天真的堆棧處理“正常工作”),但是如果我分配一個巨大的堆棧幀,那麼我可能會超出保護頁面和段錯誤,所以我想確定在程序啟動時已經正確分配了多少空間。

編輯:更多數據讓我更加不確定發生了什麼。測試如下:

.text
 .global _start
_start:
 subq $0x7fe000,%rsp
 movq $1,(%rsp)
 mov $0x3c,%eax
 mov $0,%edi
 syscall

我在0x7fe000這裡使用了不同的常量值來看看會發生什麼,對於這個值,我是否得到段錯誤是不確定的。根據 GDB 的說法,這subq條指令本身會擴大 mmap 的大小,這對我來說很神秘(linux 怎麼知道我的寄存器中有什麼?),但是這個程序通常會由於某種原因在退出時使 GDB 崩潰。不可能是 ASLR 導致不確定性,因為我沒有使用 GOT 或任何 PLT 部分;執行檔每次總是載入到虛擬記憶體中的相同位置。那麼這是 PID 或物理記憶體的一些隨機性嗎?總而言之,我很困惑到底有多少堆棧實際上合法可用於隨機訪問,以及更改 RSP 或寫入“剛剛超出範圍”的區域需要多少

我不相信這個問題真的與 ELF 有關。據我所知,ELF 定義了一種將程序映像“扁平打包”到文件中,然後重新組裝它以供首次執行的方法。如果作業系統行為尚未提升到 POSIX,那麼堆棧是什麼以及如何實現的定義位於特定於 CPU 和特定於作業系統之間。 儘管毫無疑問,ELF 規範對堆棧所需的內容提出了一些要求。

最小堆棧分配

從你的問題:

我知道堆棧下方的頁面是一個“保護頁面”,如果我寫入它,它會自動變為可寫並“在堆棧中向下增長”(大概是這樣天真的堆棧處理“正常工作”),但是如果我分配一個巨大的堆棧幀,那麼我可能會超出保護頁面和段錯誤,所以我想確定在程序啟動時已經正確分配了多少空間。

我正在努力為此尋找權威參考。但我發現足夠多的非權威參考資料表明這是不正確的。

根據我的閱讀,保護頁面用於擷取最大堆棧分配之外的訪問,而不是用於“正常”堆棧增長。實際的記憶體分配(將頁面映射到記憶體地址)是按需完成的。即:當訪問記憶體中未映射的地址時,在stack-base和stack-base - max-stack-size + 1之間,CPU可能會觸發異常,但核心將通過映射頁面來處理異常記憶體,而不是級聯分段錯誤。

因此訪問最大分配內的堆棧不應導致分段錯誤。正如你所發現的

最大堆棧分配

調查文件應該遵循關於執行緒創建和圖像載入的 Linux 文件行(fork(2)clone(2)execve(2))。 execve 的文件提到了一些有趣的事情:

參數大小和環境的限制

……剪……

在核心 2.6.23 及更高版本上,大多數體系結構都支持從軟RLIMIT_STACK資源限制派生的大小限制(請參閱getrlimit(2)

……剪……

這證實了限制需要體系結構來支持它,並且還引用了它的限制位置(getrlimit(2))。

RLIMIT_STACK

這是程序堆棧的最大大小,以字節為單位。達到此限制後,將生成 SIGSEGV 信號。要處理此信號,程序必須使用備用信號堆棧 (sigaltstack(2))。

從 Linux 2.6.23 開始,這個限制也決定了程序的命令行參數和環境變數使用的空間量;有關詳細資訊,請參閱 execve(2)。

通過更改 RSP 寄存器來增加堆棧

我不知道 x86 彙程式序。 但是我會提請您注意在更改 SS 寄存器時可以由 x86 CPU 觸發的“堆棧故障異常”。 如果我錯了,請糾正我,但我相信 x86-64 SS:SP 剛剛變成“RSP”。因此,如果我理解正確,可以通過遞減的 RSP ( subq $0x7fe000,%rsp) 觸發堆棧故障異常。

請參閱此處的第 222 頁:https ://xem.github.io/minix86/manual/intel-x86-and-64-manual-vol3/o_fe12b1e2a880e0ce.html

引用自:https://unix.stackexchange.com/questions/521960