ที่อยู่การทำงานของแอปพลิเคชัน ตามด้วยการขยายฮีปและสแต็ก

ฉันมี m.c:

extern void a(char*);

int main(int ac, char **av){
    static char string [] = "Hello , world!\n";
    a(string);
}

และ a.c:

#include <unistd.h>
#include <string.h>

void a(char* s){
    write(1, s, strlen(s));
}

ฉันรวบรวมและสร้างสิ่งเหล่านี้เป็น:

g++ -c -g -std=c++14 -MMD -MP -MF "m.o.d" -o m.o m.c
g++ -c -g -std=c++14 -MMD -MP -MF "a.o.d" -o a.o a.c
g++ -o linux m.o a.o -lm -lpthread -ldl

จากนั้น ฉันตรวจสอบไฟล์ปฏิบัติการ linux ดังนี้:

objdump -drwxCS -Mintel linux

ผลลัพธ์ของสิ่งนี้ใน Ubuntu 16.04.6 ของฉันเริ่มต้นด้วย:

start address 0x0000000000400540

ต่อมาคือส่วน init:

00000000004004c8 <_init>:
  4004c8:   48 83 ec 08             sub    rsp,0x8

สุดท้ายคือส่วน fini:

0000000000400704 <_fini>:
  400704:   48 83 ec 08             sub    rsp,0x8
  400708:   48 83 c4 08             add    rsp,0x8
  40070c:   c3                      ret 

โปรแกรมอ้างอิงสตริง Hello , world!\n ซึ่งอยู่ในส่วน .data ที่ได้รับจากคำสั่ง:

objdump -sj .data linux

Contents of section .data:
 601030 00000000 00000000 00000000 00000000  ................
 601040 48656c6c 6f202c20 776f726c 64210a00  Hello , world!..

ทั้งหมดนี้บอกผมว่า executable ถูกสร้างมาเพื่อโหลดใน address หน่วยความจำจริง โดยเริ่มจากประมาณ 0x0000000000400540 (ที่อยู่ .init) และโปรแกรมเข้าถึงข้อมูลใน address หน่วยความจำจริงขยายไปจนถึงอย่างน้อย 601040 (ที่อยู่ .data)

ฉันยึดตามบทที่ 7 ของ Linkers & Loaders โดย John R Levine โดยเขากล่าวว่า:

ตัวเชื่อมโยงจะรวมชุดของไฟล์อินพุตเป็นไฟล์เอาต์พุตเดี่ยวที่พร้อมสำหรับการโหลดไปยังที่อยู่เฉพาะ

คำถามของฉันเกี่ยวกับบรรทัดถัดไป

หากเมื่อโหลดโปรแกรมแล้ว พื้นที่เก็บข้อมูลตามที่อยู่นั้นไม่พร้อมใช้งาน ตัวโหลดจะต้องย้ายตำแหน่งโปรแกรมที่โหลดเพื่อให้สะท้อนถึงที่อยู่โหลดจริง

(1) สมมติว่าฉันมีไฟล์ปฏิบัติการอื่นที่ทำงานอยู่บนเครื่องของฉันอยู่แล้วโดยใช้พื้นที่หน่วยความจำระหว่าง 400540 ถึง 601040 จะตัดสินใจได้อย่างไรว่าจะเริ่มไฟล์ปฏิบัติการใหม่ linux จากที่ใด

(2) ที่เกี่ยวข้องกับเรื่องนี้ ในบทที่ 4 ระบุไว้ว่า

..วัตถุ ELF...ถูกโหลดไว้ตรงกลางพื้นที่ที่อยู่ ดังนั้นสแต็กสามารถขยายลงมาด้านล่างส่วนของข้อความ และฮีปสามารถขยายขึ้นจากส่วนท้ายของข้อมูล ทำให้พื้นที่ที่อยู่ทั้งหมดที่ใช้งานค่อนข้างกะทัดรัด

สมมติว่าแอปพลิเคชันที่ทำงานอยู่ก่อนหน้านี้เริ่มต้นที่ 200000 และตอนนี้ linux เริ่มต้นที่ประมาณ 400540 ไม่มีการขัดแย้งหรือทับซ้อนของที่อยู่หน่วยความจำ แต่เมื่อโปรแกรมดำเนินต่อไป สมมติว่าฮีปของแอปพลิเคชันก่อนหน้าคืบคลานขึ้นเป็น 300000 ในขณะที่สแต็กของ linux ที่เพิ่งเปิดตัวเพิ่มขึ้นลดลงเหลือ 310000 ในไม่ช้า จะมีการขัดแย้ง/ทับซ้อนกันของที่อยู่หน่วยความจำ จะเกิดอะไรขึ้นเมื่อการปะทะกันเกิดขึ้นในที่สุด?


person Tryer    schedule 06.08.2020    source แหล่งที่มา


คำตอบ (2)


หากเมื่อโหลดโปรแกรมแล้ว พื้นที่เก็บข้อมูลตามที่อยู่นั้นไม่พร้อมใช้งาน ตัวโหลดจะต้องย้ายตำแหน่งโปรแกรมที่โหลดเพื่อให้สะท้อนถึงที่อยู่โหลดจริง

ไฟล์บางรูปแบบไม่รองรับสิ่งนี้:

GCC สำหรับ Windows 32 บิตจะเพิ่มข้อมูลที่จำเป็นสำหรับตัวโหลดในกรณีของไดนามิกไลบรารี (.dll) อย่างไรก็ตาม ข้อมูลจะไม่ถูกเพิ่มลงในไฟล์ปฏิบัติการ (.exe) ดังนั้นจึงต้องโหลดไฟล์ปฏิบัติการดังกล่าวไปยังที่อยู่คงที่

ภายใต้ Linux มันจะซับซ้อนกว่าเล็กน้อย อย่างไรก็ตาม ยังไม่สามารถโหลดไฟล์ปฏิบัติการจำนวนมาก (โดยทั่วไปคือ 32 บิตที่เก่ากว่า) ไปยังที่อยู่ที่แตกต่างกันได้ ในขณะที่ไดนามิกไลบรารี (.so) สามารถโหลดไปยังที่อยู่อื่นได้

สมมติว่าฉันมีไฟล์ปฏิบัติการอื่นที่ทำงานอยู่บนเครื่องของฉันอยู่แล้วโดยใช้พื้นที่หน่วยความจำระหว่าง 400540 ถึง 601040 ...

คอมพิวเตอร์สมัยใหม่ (คอมพิวเตอร์ x86 32 บิตทั้งหมด) มี MMU แบบเพจจิ้งซึ่งระบบปฏิบัติการสมัยใหม่ส่วนใหญ่ใช้ นี่คือวงจรบางส่วน (โดยทั่วไปใน CPU) ซึ่งแปลที่อยู่ที่ซอฟต์แวร์เห็นไปเป็นที่อยู่ที่ RAM เห็น ในตัวอย่างของคุณ 400540 สามารถแปลเป็น 1234000 ได้ ดังนั้นการเข้าถึงที่อยู่ 400540 จะเข้าถึงที่อยู่ 1234000 ใน RAM จริงๆ

ประเด็นก็คือ: ระบบปฏิบัติการสมัยใหม่ใช้การกำหนดค่า MMU ที่แตกต่างกันสำหรับงานที่แตกต่างกัน ดังนั้นหากคุณเริ่มโปรแกรมอีกครั้ง ระบบจะใช้การกำหนดค่า MMU อื่นเพื่อแปลที่อยู่ 400540 ที่ซอฟต์แวร์เห็นไปเป็นที่อยู่ 2345000 ใน RAM ทั้งสองโปรแกรมที่ใช้ที่อยู่ 400540 สามารถทำงานได้พร้อมกัน เนื่องจากโปรแกรมหนึ่งจะเข้าถึงที่อยู่ 1234000 จริงๆ และอีกโปรแกรมหนึ่งจะเข้าถึงที่อยู่ 2345000 ใน RAM เมื่อโปรแกรมเข้าถึงที่อยู่ 400540

ซึ่งหมายความว่าที่อยู่บางส่วน (เช่น 400540) จะไม่ถูกใช้งานเมื่อมีการโหลดไฟล์ปฏิบัติการ

ที่อยู่อาจมีการใช้งานอยู่แล้วเมื่อมีการโหลดไลบรารีแบบไดนามิก (.so/.dll) เนื่องจากไลบรารีเหล่านี้แชร์หน่วยความจำกับไฟล์ปฏิบัติการ

... จะตัดสินใจได้อย่างไรว่าจะเริ่ม linux ที่ปฏิบัติการได้ใหม่ของฉันที่ไหน?

ภายใต้ Linux ไฟล์ปฏิบัติการจะถูกโหลดไปยังที่อยู่คงที่หากมีการเชื่อมโยงในลักษณะที่ไม่สามารถย้ายไปยังที่อยู่อื่นได้ (ดังที่ได้กล่าวไปแล้ว: นี่เป็นเรื่องปกติสำหรับไฟล์ 32 บิตรุ่นเก่า) ในตัวอย่างของคุณ สตริง Hello world จะอยู่ที่ที่อยู่ 0x601040 หาก คอมไพเลอร์และตัวเชื่อมโยงของคุณสร้างไฟล์ปฏิบัติการในลักษณะนั้น

อย่างไรก็ตาม ไฟล์ปฏิบัติการ 64 บิตส่วนใหญ่สามารถโหลดไปยังที่อยู่อื่นได้ Linux จะโหลดไปยังที่อยู่ สุ่ม เนื่องจากเหตุผลด้านความปลอดภัย ทำให้ไวรัสหรือมัลแวร์อื่นๆ โจมตีโปรแกรมได้ยากขึ้น

... เพื่อให้สแต็กสามารถขยายลงมาด้านล่างส่วนของข้อความได้ ...

ฉันไม่เคยเห็นเค้าโครงหน่วยความจำนี้ในระบบปฏิบัติการใด ๆ :

ทั้งภายใต้ Linux และภายใต้ Solaris สแต็กจะอยู่ที่ส่วนท้ายของพื้นที่ที่อยู่ (ประมาณ 0xBFFFFF00) ในขณะที่ส่วนข้อความถูกโหลดค่อนข้างใกล้กับจุดเริ่มต้นของหน่วยความจำ (อาจเป็นที่อยู่ 0x401000)

... และฮีปสามารถเติบโตได้จากจุดสิ้นสุดของข้อมูล ...

สมมติว่าฮีปของแอปพลิเคชันก่อนหน้าคืบคลานขึ้น ...

การใช้งานหลายอย่างนับตั้งแต่ช่วงปลายทศวรรษ 1990 ไม่ได้ใช้ฮีปอีกต่อไป แต่จะใช้ mmap() เพื่อสำรองหน่วยความจำใหม่แทน

ตามหน้าคู่มือของ brk() ฮีปได้รับการประกาศให้เป็นฟีเจอร์ดั้งเดิมในปี 2544 ดังนั้นจึงไม่ควรใช้กับโปรแกรมใหม่อีกต่อไป

(อย่างไรก็ตามตาม Peter Cordes malloc() ดูเหมือนว่าจะยังคงใช้ฮีปในบางกรณี)

ต่างจากระบบปฏิบัติการทั่วไปอย่าง MS-DOS ตรงที่ Linux ไม่อนุญาตให้คุณใช้ฮีป แต่คุณต้องเรียกใช้ฟังก์ชัน brk() เพื่อบอก Linux ว่าคุณต้องการใช้ฮีปจำนวนเท่าใด

หากโปรแกรมใช้ฮีปและใช้ฮีปมากกว่าที่มีอยู่ ฟังก์ชัน brk() จะส่งกลับรหัสข้อผิดพลาดบางส่วน และฟังก์ชัน malloc() จะส่งคืน NULL

อย่างไรก็ตาม สถานการณ์นี้มักเกิดขึ้นเนื่องจากไม่มี RAM เหลืออยู่ และไม่ใช่เนื่องจากฮีปซ้อนทับกับพื้นที่หน่วยความจำอื่น

... ในขณะที่สแต็คของ linux ที่เพิ่งเปิดตัวกลับลดลงเหลือ ...

ในไม่ช้า จะมีการขัดแย้ง/ทับซ้อนกันของที่อยู่หน่วยความจำ จะเกิดอะไรขึ้นเมื่อการปะทะกันเกิดขึ้นในที่สุด?

แท้จริงแล้วขนาดของสแต็กนั้นมีจำกัด

หากคุณใช้สแต็กมากเกินไป คุณจะมีสแต็กล้น

โปรแกรมนี้ตั้งใจใช้สแต็กมากเกินไป - เพียงเพื่อดูว่าจะเกิดอะไรขึ้น:

.globl _start
_start:
    sub $0x100000, %rsp
    push %rax
    push %rax
    jmp _start

ในกรณีของระบบปฏิบัติการที่มี MMU (เช่น Linux) โปรแกรมของคุณจะขัดข้องพร้อมข้อความแสดงข้อผิดพลาด:

~$ ./example_program
Segmentation fault (core dumped)
~$

แก้ไข/เพิ่มเติม

สแต็กสำหรับโปรแกรมที่รันอยู่ทั้งหมดอยู่ที่ส่วนท้ายหรือไม่

ใน Linux เวอร์ชันเก่า สแตกตั้งอยู่ใกล้ (แต่ไม่ตรงทั้งหมด) จุดสิ้นสุดของหน่วยความจำ เสมือน ที่โปรแกรมเข้าถึงได้: โปรแกรมสามารถเข้าถึงช่วงที่อยู่ตั้งแต่ 0 ถึง 0xBFFFFFFF ใน Linux เวอร์ชันเหล่านั้น ตัวชี้สแต็กเริ่มต้นอยู่ที่ประมาณ 0xBFFFFE00 (อาร์กิวเมนต์บรรทัดคำสั่งและตัวแปรสภาพแวดล้อมอยู่หลังสแต็ก)

และนี่คือจุดสิ้นสุดของหน่วยความจำกายภาพที่แท้จริงหรือไม่? โปรแกรมที่รันอยู่หลายๆ โปรแกรมจะไม่ปะปนกันใช่ไหม? ฉันรู้สึกว่าสแต็กและหน่วยความจำทั้งหมดของโปรแกรมยังคงต่อเนื่องกันในหน่วยความจำกายภาพจริง ...

บนคอมพิวเตอร์ที่ใช้ MMU โปรแกรมจะไม่เห็นหน่วยความจำกายภาพเลย:

เมื่อโหลดโปรแกรมแล้ว ระบบปฏิบัติการจะค้นหาพื้นที่ว่างของ RAM - อาจพบบางส่วนที่ที่อยู่ทางกายภาพ 0xABC000 จากนั้นจะกำหนดค่า MMU ในลักษณะที่ที่อยู่เสมือน 0xBFFFF000-0xBFFFFFFF ได้รับการแปลเป็นที่อยู่จริง 0xABC000-0xABCFFF

ซึ่งหมายความว่า: เมื่อใดก็ตามที่โปรแกรมเข้าถึงที่อยู่ 0xBFFFFE20 (เช่น การใช้การดำเนินการ push) ที่อยู่จริง 0xABCE20 ใน RAM ก็จะถูกเข้าถึงจริง

ไม่มีความเป็นไปได้ที่โปรแกรมจะเข้าถึงที่อยู่ทางกายภาพที่แน่นอน

หากคุณมีโปรแกรมอื่นที่ทำงานอยู่ MMU จะได้รับการกำหนดค่าในลักษณะที่ที่อยู่ 0xBFFFF000-0xBFFFFFFF ได้รับการแปลเป็นที่อยู่ 0x345000-0x345FFF เมื่อโปรแกรมอื่นกำลังทำงานอยู่

ดังนั้นหากหนึ่งในสองโปรแกรมจะดำเนินการ push และตัวชี้สแต็กคือ 0xBFFFFE20 ที่อยู่ 0xABCE20 ใน RAM ก็จะสามารถเข้าถึงได้ หากโปรแกรมอื่นดำเนินการ push (ด้วยค่าตัวชี้สแต็กเดียวกัน) ที่อยู่ 0x345E20 จะถูกเข้าถึง

ดังนั้นสแต็คจะไม่ปะปนกัน

ระบบปฏิบัติการที่ไม่ได้ใช้ MMU แต่รองรับการทำงานหลายอย่างพร้อมกัน (เช่น Amiga 500 หรือ Apple Macintoshes รุ่นแรกๆ) จะไม่ทำงานในลักษณะนี้ ระบบปฏิบัติการดังกล่าวใช้รูปแบบไฟล์พิเศษ (ไม่ใช่ ELF) ซึ่งได้รับการปรับให้เหมาะสมสำหรับการรันหลายโปรแกรมโดยไม่มี MMU การคอมไพล์โปรแกรมสำหรับระบบปฏิบัติการดังกล่าวนั้นซับซ้อนกว่าการคอมไพล์โปรแกรมสำหรับ Linux หรือ Windows มาก และยังมีข้อจำกัดสำหรับนักพัฒนาซอฟต์แวร์ด้วย (ตัวอย่าง: ฟังก์ชันและอาร์เรย์ไม่ควรยาวเกินไป)

แต่ละโปรแกรมมีตัวชี้สแต็ก ตัวชี้ฐาน รีจิสเตอร์ ฯลฯ ของตัวเองหรือไม่ หรือระบบปฏิบัติการมีรีจิสเตอร์เหล่านี้ชุดเดียวที่จะแชร์โดยทุกโปรแกรม?

(สมมติว่าเป็นซีพียูแบบคอร์เดียว) CPU มีรีจิสเตอร์หนึ่งชุด และสามารถทำงานได้เพียงโปรแกรมเดียวเท่านั้น

เมื่อคุณเริ่มหลายโปรแกรม ระบบปฏิบัติการจะสลับระหว่างโปรแกรมต่างๆ ซึ่งหมายความว่าโปรแกรม A จะทำงานเป็นเวลา 1/50 วินาที จากนั้นโปรแกรม B จะทำงานเป็นเวลา 1/50 วินาที จากนั้นโปรแกรม A จะทำงานเป็นเวลา 1/50 วินาทีและอื่นๆ ปรากฏให้คุณเห็นว่าโปรแกรมทำงานในเวลาเดียวกัน

เมื่อระบบปฏิบัติการเปลี่ยนจากโปรแกรม A เป็นโปรแกรม B จะต้องบันทึกค่าของรีจิสเตอร์ (ของโปรแกรม A) ก่อน จากนั้นจะต้องเปลี่ยนการกำหนดค่า MMU สุดท้ายจะต้องคืนค่ารีจิสเตอร์ของโปรแกรม B

person Martin Rosenau    schedule 06.08.2020
comment
Linux distros สมัยใหม่สร้าง PIE แบบ 32 บิต คุณบอกว่าไฟล์ปฏิบัติการ Linux 64 บิตมักจะย้ายตำแหน่งได้ แต่ไฟล์ปฏิบัติการ 32 บิตส่วนใหญ่ไม่เป็นเช่นนั้น นั่นเป็นเพียงการพิจารณาน้ำหนักของประวัติศาสตร์หรือไม่? x86-64 แพร่หลายมานานหลายปีก่อนที่โปรแกรมปฏิบัติการ PIE จะเริ่มกลายเป็นสิ่งหนึ่ง เช่น. Ubuntu 16.04 ของ OP ทำให้สามารถเรียกใช้งานได้แบบ non-PIE ตามค่าเริ่มต้น สิ่งเหล่านั้นไม่สามารถเป็น ASLRed GCC จะใช้คำแนะนำเช่น mov edi, offset .LC0 เพื่อใส่ที่อยู่แบบคงที่ลงในการลงทะเบียน เนื่องจากรูปแบบโค้ดที่ไม่ใช่ PIE เริ่มต้นจะรับประกันว่าโค้ด/ข้อมูลแบบคงที่จะอยู่ในพื้นที่ที่อยู่ 31 บิตต่ำ - person Peter Cordes; 06.08.2020
comment
คุณกำลังพูดถึงโปรแกรมของ OP ในย่อหน้าเดียวกับ 32 บิต เป็นแบบ 64 บิต ดังที่เราบอกได้อย่างแน่นอนจากการถอดแยกชิ้นส่วน นอกจากนี้ binutils ld ยังมีที่อยู่พื้นฐานเริ่มต้นที่แตกต่างกันสำหรับ .text ในโหมด 32 บิต โปรแกรมของ OP เป็นโปรแกรมปฏิบัติการที่ไม่ใช่ PIE (ELF ประเภท EXEC) x86-64 ไม่สามารถย้ายตำแหน่งได้: ไม่มีข้อมูลเมตาการย้ายตำแหน่งเพื่อใช้การแก้ไขกับข้อมูลหรือโค้ดคงที่ และไม่มีข้อกำหนดที่ไม่ขึ้นอยู่กับตำแหน่ง - person Peter Cordes; 06.08.2020
comment
glibc ปัจจุบัน malloc ยังคงใช้ brk สำหรับการจัดสรรขนาดเล็ก mmap สำหรับการจัดสรรจำนวนมาก (ดังนั้นจึงสามารถคืนเพจต่างๆ ให้กับระบบปฏิบัติการได้อย่างแน่นอน โดยไม่ติดขัดกับรายการว่าง) มีฮิวริสติกการปรับแต่ง IIRC ทางลัดคือสองสามหน้าหรืออาจจะถึง 64k ด้วยซ้ำ strace ls และเห็นว่าใช้ brk syscalls บางส่วน (แน่นอนว่า ประเด็น โดยรวมของคำตอบของคุณนั้นถูกต้อง หน่วยความจำเสมือนทำให้ไม่มีปัญหา แต่น่าเสียดายที่รายละเอียดบางอย่างไม่ถูกต้อง) - person Peter Cordes; 06.08.2020
comment
@PeterCordes ฉันคิดว่า x86 Linux distros ที่ทันสมัยที่สุดคือ 64 บิตซึ่งมักจะไม่รองรับโปรแกรม 32 บิตโดยไม่ต้องติดตั้งแพ็คเกจเพิ่มเติม ดังนั้นเวลาเขียนเกี่ยวกับโปรแกรม 32 บิต ฉันหมายถึงโปรแกรมเก่าๆ ดังนั้น คำว่า generic จึงหมายถึงโปรแกรมเฉลี่ยในปี 1995-2018 ไม่ใช่โปรแกรมเฉลี่ยในหนึ่งใน distro 32 บิตไม่กี่ตัวที่ยังคงมีอยู่ - person Martin Rosenau; 06.08.2020
comment
@PeterCordes ฉันอัปเดตประโยคเกี่ยวกับไฟล์ปฏิบัติการแบบ 32 บิตในคำตอบของฉัน ฉันยังเพิ่มคำอธิบายเกี่ยวกับวิธีการใช้ฮีปใน Linux และจะเกิดอะไรขึ้นหากไม่มีฮีปอีกต่อไป - person Martin Rosenau; 06.08.2020
comment
@MartinRosenau เป็นสแต็กสำหรับโปรแกรมที่รันอยู่ทั้งหมดที่อยู่ตอนท้ายหรือไม่ และนี่คือจุดสิ้นสุดของหน่วยความจำกายภาพที่แท้จริงหรือไม่? โปรแกรมที่รันอยู่หลายๆ โปรแกรมจะไม่ปะปนกันใช่ไหม? ฉันรู้สึกว่าสแต็กและหน่วยความจำทั้งหมดของโปรแกรมยังคงต่อเนื่องกันในหน่วยความจำกายภาพจริง โดยจะเพิ่มขึ้นและลดลงตามความจำเป็น แต่ยังคงต่อเนื่องกัน แต่ละโปรแกรมมีตัวชี้สแต็ก ตัวชี้ฐาน รีจิสเตอร์ ฯลฯ ของตัวเองหรือไม่ หรือระบบปฏิบัติการมีรีจิสเตอร์เหล่านี้ชุดเดียวที่จะแชร์โดยทุกโปรแกรม? - person Tryer; 07.08.2020
comment
@Tryer โปรดดูส่วนแก้ไขของฉันในคำตอบของฉัน - person Martin Rosenau; 07.08.2020

ใช่ objdump บนไฟล์ปฏิบัติการนี้จะแสดงที่อยู่ซึ่งเซ็กเมนต์จะถูกแมป (การเชื่อมโยงรวบรวมส่วนต่างๆ ออกเป็นส่วนๆ: ความแตกต่างคืออะไร ของส่วนและส่วนในรูปแบบไฟล์ ELF) .data และ .text เชื่อมโยงไปยังส่วนต่างๆ ด้วยสิทธิ์ที่แตกต่างกัน (อ่าน+เขียน กับ อ่าน+ดำเนินการ)

หากเมื่อโหลดโปรแกรมแล้ว พื้นที่เก็บข้อมูลตามที่อยู่นั้นไม่พร้อมใช้งาน

ซึ่งอาจเกิดขึ้นเมื่อโหลดไลบรารีแบบไดนามิกเท่านั้น ไม่ใช่ตัวปฏิบัติการเอง หน่วยความจำเสมือนหมายความว่าแต่ละกระบวนการมีพื้นที่ที่อยู่เสมือนส่วนตัวของตัวเอง แม้ว่าจะเริ่มต้นจากปฏิบัติการเดียวกันก็ตาม (นี่คือสาเหตุที่ ld สามารถเลือกที่อยู่พื้นฐานเริ่มต้นเดียวกันสำหรับเซ็กเมนต์ text และ data ได้ตลอดเวลา โดยไม่ได้พยายามสล็อตไฟล์ปฏิบัติการและไลบรารีทั้งหมดบนระบบไปยังจุดอื่นในพื้นที่ที่อยู่เดียว)

ไฟล์ปฏิบัติการคือสิ่งแรกที่จะอ้างสิทธิ์ในบางส่วนของพื้นที่ที่อยู่นั้น เมื่อโหลด/แมปโดยตัวโหลดโปรแกรม ELF ของระบบปฏิบัติการ นั่นเป็นเหตุผลว่าทำไมไฟล์ปฏิบัติการ ELF แบบดั้งเดิม (ไม่ใช่ PIE) จึงไม่สามารถย้ายตำแหน่งได้ ซึ่งแตกต่างจากอ็อบเจ็กต์ที่ใช้ร่วมกันของ ELF เช่น /lib/libc.so.6

หากคุณทำขั้นตอนเดียวในโปรแกรมด้วยดีบักเกอร์ หรือรวมโหมดสลีป คุณจะมีเวลาดูที่ less /proc/<PID>/maps หรือ cat /proc/self/maps เพื่อให้แมวแสดงแผนที่ของตัวเองให้คุณดู (และ /proc/self/smaps สำหรับข้อมูลรายละเอียดเพิ่มเติมเกี่ยวกับการแมปแต่ละรายการ เช่น ความสกปรกมากน้อยเพียงใด การใช้เพจขนาดใหญ่ เป็นต้น)

(GNU/Linux distros ที่ใหม่กว่ากำหนดค่า GCC เพื่อให้ปฏิบัติการ PIE ตามค่าเริ่มต้น: ไม่อนุญาตให้ใช้ที่อยู่แบบสัมบูรณ์แบบ 32 บิตใน x86-64 Linux อีกต่อไป ในกรณีนั้น objdump จะเห็นเฉพาะที่อยู่ที่สัมพันธ์กับฐานของ 0 หรือ 1000 หรือบางอย่าง และ asm ที่สร้างโดยคอมไพเลอร์ก็จะมี ใช้การกำหนดแอดเดรสแบบสัมพันธ์กับพีซี ไม่ใช่แบบสัมบูรณ์)

person Peter Cordes    schedule 06.08.2020