2010-02-02 39 views
16

Khi được thực hiện, chương trình sẽ bắt đầu chạy từ địa chỉ ảo 0x80482c0. Địa chỉ này không trỏ đến quy trình main() của chúng tôi, mà là một thủ tục có tên là _start được tạo bởi trình liên kết.Tại sao địa chỉ ảo thực thi ELF có dạng 0x80xxxxx và không phải là 0x0?

nghiên cứu của Google của tôi cho đến nay chỉ dẫn tôi đến một số (mơ hồ) suy đoán lịch sử như thế này:

Có văn hóa dân gian mà 0x08048000 từng là STACK_TOP (có nghĩa là, chồng lớn xuống từ gần 0x08048000 về phía 0) trên một cổng * NIX đến i386 do một nhóm từ Santa Cruz, California ban hành. Đây là khi RAM 128MB đắt và RAM 4GB không thể tưởng tượng được.

Có ai xác nhận/từ chối điều này không?

+0

Nếu '0x08048000' từng là' STACK_TOP', nó đã là _very_ lâu rồi. Cái sau là 'TASK_SIZE' tất cả đường đến [2.0.40] (http://lxr.free-electrons.com/source/include/asm-i386/a.out.h?v=2.0.40#L22) . –

Trả lời

31

Như Mads đã chỉ ra, để nắm bắt hầu hết các truy cập thông qua các con trỏ null, các hệ thống giống Unix có xu hướng làm cho trang tại địa chỉ zero "chưa được ánh xạ". Do đó, truy cập ngay lập tức kích hoạt một ngoại lệ CPU, nói cách khác là một segfault. Điều này là khá tốt hơn cho phép các ứng dụng đi giả mạo. Tuy nhiên, bảng vectơ ngoại lệ có thể ở bất kỳ địa chỉ nào, ít nhất là trên các bộ xử lý x86 (có một thanh ghi đặc biệt cho nó, được nạp bằng mã opcode lidt).

Địa chỉ điểm xuất phát là một phần của tập hợp các quy ước mô tả cách bộ nhớ được đặt ra. Trình liên kết, khi nó tạo ra một nhị phân thực thi, phải biết các quy ước này, vì vậy chúng không có khả năng thay đổi. Về cơ bản, đối với Linux, các quy ước bố trí bộ nhớ được kế thừa từ các phiên bản Linux đầu tiên, vào đầu những năm 90. Quá trình phải có quyền truy cập vào một số khu vực:

  • Mã phải nằm trong phạm vi bao gồm điểm xuất phát.
  • Phải có ngăn xếp.
  • Phải có một đống, với giới hạn được tăng lên với các cuộc gọi hệ thống brk()sbrk().
  • Phải có một số phòng cho mmap() cuộc gọi hệ thống, bao gồm cả tải thư viện được chia sẻ.

Ngày nay, heap, nơi malloc() được bật, được hỗ trợ bởi mmap() cuộc gọi có khối bộ nhớ tại bất kỳ địa chỉ nào mà hạt nhân thấy phù hợp. Nhưng trong thời đại cũ hơn, Linux giống như các hệ thống giống Unix trước đây, và đống của nó đòi hỏi một khu vực rộng lớn trong một đoạn không bị gián đoạn, có thể phát triển theo hướng tăng địa chỉ. Vì vậy, bất cứ điều gì đã được quy ước, nó đã để nhồi mã và ngăn xếp đối với địa chỉ thấp, và cung cấp cho mỗi đoạn của không gian địa chỉ sau một điểm nhất định cho đống.

Nhưng cũng có ngăn xếp, thường khá nhỏ nhưng có thể tăng lên đáng kể trong một số trường hợp. Ngăn xếp phát triển và khi ngăn xếp đầy, chúng tôi thực sự muốn quy trình dự đoán sự cố thay vì ghi đè một số dữ liệu. Vì vậy, phải có một diện tích rộng cho ngăn xếp, với, ở cuối thấp của khu vực đó, một trang chưa được ánh xạ. Và lo! Có một trang chưa được ánh xạ tại địa chỉ 0, để bắt các tham số con trỏ null. Do đó nó đã được xác định rằng ngăn xếp sẽ nhận được 128 MB đầu tiên của không gian địa chỉ, ngoại trừ trang đầu tiên. Điều này có nghĩa là mã phải đi sau 128 MB đó, tại một địa chỉ tương tự như 0x080xxxxx.

Như Michael chỉ ra, "mất" 128 MB không gian địa chỉ là không lớn vì không gian địa chỉ là rất lớn liên quan đến những gì có thể thực sự được sử dụng. Vào thời điểm đó, hạt nhân Linux đã hạn chế không gian địa chỉ cho một quá trình duy nhất đến 1 GB, vượt quá tối đa 4 GB cho phép bởi phần cứng và điều đó không được coi là một vấn đề lớn.

6

Tại sao không bắt đầu tại địa chỉ 0x0? Có ít nhất hai lý do cho việc này:

  • Vì địa chỉ 0 được biết đến là con trỏ NULL và được sử dụng bởi ngôn ngữ lập trình cho con trỏ kiểm tra lành mạnh. Bạn không thể sử dụng một giá trị địa chỉ cho điều đó, nếu bạn định thực thi mã ở đó.
  • Nội dung thực tế tại địa chỉ 0 thường (nhưng không phải luôn luôn) bảng vectơ ngoại lệ và do đó không thể truy cập được ở chế độ không có đặc quyền. Tham khảo tài liệu về kiến ​​trúc cụ thể của bạn.

Đối với entrypoint _start vs main: Nếu bạn liên kết chống lại thời gian chạy C (các thư viện chuẩn C), thư viện kết thúc tốt đẹp các hàm có tên main, vì vậy nó có thể khởi tạo môi trường trước khi main được gọi. Trên Linux, đây là các thông số argcargv cho ứng dụng, các biến số env và có thể một số nguyên tắc đồng bộ hóa và khóa. Nó cũng đảm bảo rằng quay trở lại từ các pass chính trên mã trạng thái, và gọi hàm _exit, kết thúc quá trình.

+5

Trong C con trỏ null có thể có giá trị hoàn toàn khác với 0 ở mức thấp nhất. Trong phạm vi của C (mã nguồn) giá trị con trỏ không hợp lệ của máy phải ánh xạ tới 0. Về mặt kỹ thuật, không có yêu cầu nào cho con trỏ null C thực sự ánh xạ tới địa chỉ 0. – datenwolf

+0

'_GLOBAL_OFFSET_TABLE_' cũng chỉ trong phạm vi' 0x200XXX' trong Binutils 2.24. –

Các vấn đề liên quan