Bách khoa toàn thư về an toàn cháy nổ

Cách mở tệp elf trong windows. Phần mở rộng Tệp .ELF là gì? Các nút mô tả dữ liệu

Các công cụ phát triển tiêu chuẩn biên dịch chương trình của bạn thành tệp ELF (Định dạng có thể thực thi và có thể liên kết) với tùy chọn bao gồm thông tin gỡ lỗi. Thông số định dạng có thể được đọc. Ngoài ra, mỗi kiến ​​trúc có những đặc điểm riêng, chẳng hạn như của ARM. Chúng ta hãy xem nhanh định dạng này.
Một tệp thực thi có định dạng ELF bao gồm các phần sau:
1. Tiêu đề (ELF Header)
Chứa thông tin chung về tệp và các đặc điểm chính của tệp.
2. Bảng tiêu đề chương trình
Đây là một bảng tương ứng của các phần tệp với các phân đoạn bộ nhớ, cho bộ tải biết vùng bộ nhớ nào để ghi mỗi phần.
3. Phần
Các phần chứa tất cả thông tin trong tệp (chương trình, dữ liệu, thông tin gỡ lỗi, v.v.)
Mỗi phần có một loại, một tên và các tham số khác. Phần ".text" thường lưu trữ mã, ".symtab" - bảng ký hiệu chương trình (tên tệp, thủ tục và biến), ".strtab" - bảng chuỗi, phần có tiền tố ".debug_" - thông tin gỡ lỗi, v.v. d. Ngoài ra, tệp phải có phần trống với chỉ mục 0.
4. Bảng tiêu đề phần
Đây là một bảng chứa một mảng các tiêu đề phần.
Định dạng được thảo luận chi tiết hơn trong phần Tạo ELF.

Tổng quan về DWARF

DWARF là một định dạng thông tin gỡ lỗi được tiêu chuẩn hóa. Tiêu chuẩn có thể được tải xuống từ trang web chính thức. Ngoài ra còn có một tổng quan tuyệt vời về định dạng: Giới thiệu về Định dạng Gỡ lỗi DWARF (Michael J. Eager).
Tại sao cần thông tin gỡ lỗi? Nó cho phép:
  • đặt các điểm ngắt không phải trên địa chỉ thực mà trên số dòng trong tệp mã nguồn hoặc trên tên hàm
  • hiển thị và thay đổi giá trị của các biến cục bộ và toàn cục, cũng như các tham số hàm
  • hiển thị ngăn xếp cuộc gọi (backtrace)
  • thực hiện chương trình từng bước không phải bằng một lệnh hợp ngữ, mà bằng các dòng mã nguồn
Thông tin này được lưu trữ trong một cấu trúc cây. Mỗi nút cây có cha, có thể có con và được gọi là Mục nhập thông tin gỡ lỗi (DIE). Mỗi nút có thẻ (loại) riêng và danh sách các thuộc tính (thuộc tính) mô tả nút. Các thuộc tính có thể chứa bất kỳ thứ gì, chẳng hạn như dữ liệu hoặc liên kết đến các nút khác. Ngoài ra, có thông tin được lưu trữ bên ngoài cây.
Các nút được chia thành hai loại chính: nút mô tả dữ liệu và nút mô tả mã.
Các nút mô tả dữ liệu:
  1. Loại dữ liệu:
    • Các kiểu dữ liệu cơ sở (một nút có kiểu DW_TAG_base_type), chẳng hạn như kiểu int trong C.
    • Kiểu dữ liệu tổng hợp (con trỏ, v.v.)
    • Mảng
    • Cấu trúc, lớp học, liên hiệp, giao diện
  2. Đối tượng dữ liệu:
    • hằng số
    • tham số chức năng
    • biến
    • vân vân.
Mỗi đối tượng dữ liệu có một thuộc tính DW_AT_location chỉ định cách tính địa chỉ nơi dữ liệu cư trú. Ví dụ, một biến có thể có một địa chỉ cố định, nằm trong một thanh ghi hoặc trên ngăn xếp, là một thành viên của một lớp hoặc một đối tượng. Địa chỉ này có thể được tính toán theo một cách khá phức tạp, vì vậy tiêu chuẩn cung cấp cho cái gọi là Biểu thức Vị trí, có thể chứa một chuỗi các câu lệnh từ một máy ngăn xếp nội bộ đặc biệt.
Các nút mô tả mã:
  1. Thủ tục (chức năng) - các nút có thẻ DW_TAG_ghi chương trình phụ. Các nút con cháu có thể chứa các mô tả về các biến - tham số hàm và các biến cục bộ của hàm.
  2. Đơn vị biên soạn. Chứa thông tin về chương trình và là nút cha của tất cả các nút khác.
Thông tin được mô tả ở trên nằm trong phần ".debug_info" và ".debug_abbrev".
Thông tin khác:
  • Thông tin về số dòng (phần ".debug_line")
  • Thông tin macro (phần ".debug_macinfo")
  • Thông tin Khung cuộc gọi (phần ".debug_frame")

Sáng tạo ELF

Chúng tôi sẽ tạo tệp EFL bằng thư viện libelf từ gói elfutils. Có một bài viết hay trên web về cách sử dụng libelf - LibELF bằng Ví dụ (thật không may, việc tạo tệp được mô tả rất ngắn gọn trong đó) cũng như tài liệu.
Tạo một tệp bao gồm một số bước:
  1. tự khởi tạo
  2. Tạo tiêu đề tệp (ELF Header)
  3. Tạo bảng tiêu đề chương trình
  4. Tạo phần
  5. Viết tệp
Xem xét các bước chi tiết hơn
tự khởi tạo
Trước tiên, bạn sẽ cần gọi hàm elf_version (EV_CURRENT) và kiểm tra kết quả. Nếu nó bằng EV_NONE, thì đã xảy ra lỗi và không thể thực hiện thêm hành động nào. Sau đó, chúng ta cần tạo tệp chúng ta cần trên đĩa, lấy bộ mô tả của nó và chuyển nó vào hàm elf_begin:
Elf * elf_begin (int fd, Elf_Cmd cmd, Elf * elf)
  • fd - bộ mô tả tệp của tệp mới mở
  • cmd - mode (ELF_C_READ để đọc thông tin, ELF_C_WRITE để ghi hoặc ELF_C_RDWR để đọc / ghi), nó phải phù hợp với chế độ của tệp đang mở (ELF_C_WRITE trong trường hợp của chúng tôi)
  • elf - chỉ cần thiết để làm việc với các tệp lưu trữ (.a), trong trường hợp của chúng tôi, bạn cần vượt qua 0
Hàm trả về một con trỏ đến xử lý đã tạo sẽ được sử dụng trong tất cả các hàm của chính mình, 0 được trả về do lỗi.
Tạo tiêu đề
Tiêu đề tệp mới được tạo bởi hàm elf32_newehdr:
Elf32_Ehdr * elf32_newehdr (Elf * elf);
  • elf - xử lý được trả về bởi hàm elf_begin
Trả về 0 khi có lỗi hoặc một con trỏ đến một cấu trúc - Tiêu đề tệp ELF:
#define EI_NIDENT 16 typedef struct ( unsigned char e_ident; Elf32_Half e_type; Elf32_Half e_machine; Elf32_Word e_version; Elf32_Addr e_entry; Elf32_Off e_phoff; Elf32_Off e_shoff; Elf32_Word e_flags; Elf32_Half e_ehsize; Elf32_Half e_phentsize; Elf32_Half e_phnum; Elf32_Half e_shentsize; Elf32_Half e_shnum; Elf32_Half e_shstrndx; ) Elf32_Ehdr;

Một số trường của nó được điền theo cách chuẩn, một số trường chúng ta cần điền:

  • e_ident - mảng nhận dạng byte, có các chỉ mục sau:
    • EI_MAG0, EI_MAG1, EI_MAG2, EI_MAG3 - 4 byte này phải chứa các ký tự 0x7f, "ELF", mà hàm elf32_newehdr đã thực hiện cho chúng tôi
    • EI_DATA - cho biết kiểu mã hóa dữ liệu trong tệp: ELFDATA2LSB hoặc ELFDATA2MSB. Bạn cần đặt ELFDATA2LSB như sau: e_ident = ELFDATA2LSB
    • EI_VERSION - phiên bản tiêu đề tệp, đã được đặt cho chúng tôi
    • EI_PAD - không chạm vào
  • e_type - loại tệp, có thể là ET_NONE - không có loại, ET_REL - tệp có thể di dời, ET_EXEC - tệp thực thi, ET_DYN - tệp đối tượng được chia sẻ, v.v. Chúng tôi cần đặt loại tệp thành ET_EXEC
  • e_machine - kiến ​​trúc bắt buộc cho tệp này, ví dụ EM_386 - cho kiến ​​trúc Intel, cho ARM, chúng tôi cần viết ở đây EM_ARM (40) - xem ELF cho Kiến trúc ARM
  • e_version - phiên bản tệp, phải được đặt thành EV_CURRENT
  • e_entry - địa chỉ điểm vào, không cần thiết đối với chúng tôi
  • e_phoff - bù đắp trong tệp tiêu đề chương trình, e_shoff - bù đắp tiêu đề phần, không điền
  • e_flags - cờ cụ thể của bộ xử lý, cho kiến ​​trúc của chúng tôi (Cortex-M3) nên được đặt thành 0x05000000 (ABI phiên bản 5)
  • e_ehsize, e_phentsize, e_phnum, e_shentsize, e_shnum - không chạm vào
  • e_shstrndx - chứa số phần trong đó bảng chuỗi có tiêu đề phần được đặt. Vì chúng tôi chưa có bất kỳ phần nào nên chúng tôi sẽ đặt con số này sau.
Tạo tiêu đề chương trình
Như đã đề cập, Bảng tiêu đề chương trình là một bảng tương ứng giữa các phần tệp và các phân đoạn bộ nhớ, bảng này cho bộ tải biết nơi ghi từng phần. Tiêu đề được tạo bằng hàm elf32_newphdr:
Elf32_Phdr * elf32_newphdr (Elf * elf, số lượng size_t);
  • elf - tay cầm của chúng tôi
  • count - số phần tử bảng cần tạo. Vì chúng ta sẽ chỉ có một phần (với mã chương trình), khi đó số lượng sẽ bằng 1.
Trả về 0 khi bị lỗi hoặc một con trỏ đến tiêu đề chương trình.
Mỗi phần tử trong bảng tiêu đề được mô tả theo cấu trúc sau:
typedef struct (Elf32_Word p_type; Elf32_Off p_offset; Elf32_Addr p_vaddr; Elf32_Addr p_paddr; Elf32_Word p_filesz; Elf32_Word p_memsz; Elf32_Word p_flags; Elf32Phdr)
  • p_type - loại phân đoạn (phần), ở đây chúng ta phải chỉ định PT_LOAD - phân đoạn có thể tải
  • p_offset - các hiệu số trong tệp mà dữ liệu của phần sẽ được tải vào bộ nhớ bắt đầu. Chúng ta có một phần .text, phần này sẽ nằm ngay sau tiêu đề tệp và tiêu đề chương trình, chúng ta có thể tính toán phần bù là tổng độ dài của các tiêu đề này. Có thể lấy độ dài của bất kỳ kiểu nào bằng cách sử dụng hàm elf32_fsize:
    size_t elf32_fsize (Elf_Type type, size_t count, unsigned int version); type - đây là hằng số ELF_T_xxx, chúng ta sẽ cần các kích thước ELF_T_EHDR và ​​ELF_T_PHDR; count - số phần tử của loại mong muốn, phiên bản - phải được đặt thành EV_CURRENT
  • p_vaddr, p_paddr - địa chỉ ảo và thực nơi nội dung của phần sẽ được tải. Vì chúng tôi không có địa chỉ ảo, chúng tôi đặt nó bằng địa chỉ vật lý, trong trường hợp đơn giản nhất - 0, vì đây là nơi chương trình của chúng tôi sẽ được tải.
  • p_filesz, p_memsz - kích thước phần trong tệp và bộ nhớ. Chúng tôi có chúng giống nhau, nhưng vì chưa có phần có mã chương trình, chúng tôi sẽ cài đặt chúng sau
  • p_flags - quyền đối với phân đoạn bộ nhớ đã tải. Có thể là PF_R - đọc, PF_W - ghi, PF_X - thực thi hoặc kết hợp cả hai. Đặt p_flags thành PF_R + PF_X
  • p_align - căn chỉnh phân đoạn, chúng ta có 4
Tạo phần
Sau khi tạo các tiêu đề, bạn có thể bắt đầu tạo các phần. Một phần trống được tạo bằng hàm elf_newscn:
Elf_Scn * elf_newscn (Yêu tinh * elf);
  • elf - xử lý được trả về trước đó bởi hàm elf_begin
Hàm trả về một con trỏ phần hoặc 0 khi bị lỗi.
Sau khi tạo phần, bạn cần điền vào tiêu đề phần và tạo bộ mô tả dữ liệu phần.
Chúng ta có thể nhận được một con trỏ đến tiêu đề phần bằng cách sử dụng hàm elf32_getshdr:
Elf32_Shdr * elf32_getshdr (Elf_Scn * scn);
  • scn là con trỏ phần mà chúng ta nhận được từ hàm elf_newscn.
Tiêu đề phần trông như thế này:
typedef struct (Elf32_Word sh_type; Elf32_Word sh_type; Elf32_Word sh_flags; Elf32_Addr sh_addr; Elf32_Off sh_offset; Elf32_Word sh_size; Elf32_Word sh_link; Elf32_Word sh_ize_Jord
  • sh_name - tên phần - phần bù trong bảng chuỗi của tiêu đề phần (section.shstrtab) - xem "Bảng chuỗi" bên dưới
  • sh_type - loại nội dung phần, đặt SHT_PROGBITS cho phần có mã chương trình, SHT_STRTAB cho các phần có bảng chuỗi, SHT_SYMTAB cho bảng ký hiệu
  • sh_flags - cờ phần có thể được kết hợp và chúng tôi chỉ cần ba cờ:
    • SHF_ALLOC - nghĩa là phần sẽ được tải vào bộ nhớ
    • SHF_EXECINSTR - phần chứa mã thực thi
    • SHF_STRINGS - phần chứa bảng chuỗi
    Theo đó, đối với phần .text với chương trình, bạn cần đặt cờ SHF_ALLOC + SHF_EXECINSTR
  • sh_addr - địa chỉ nơi phần sẽ được tải vào bộ nhớ
  • sh_offset - phần bù đắp trong tệp - không chạm vào, thư viện sẽ cài đặt cho chúng tôi
  • sh_size - kích thước phần - không chạm vào
  • sh_link - chứa số của phần được liên kết, cần thiết để liên kết phần đó với bảng chuỗi tương ứng (xem bên dưới)
  • sh_info - thông tin bổ sung tùy thuộc vào loại phần, được đặt thành 0
  • sh_addralign - căn chỉnh địa chỉ, không chạm vào
  • sh_entsize - nếu phần bao gồm một số phần tử có cùng độ dài, cho biết độ dài của phần tử đó, không chạm vào
Sau khi điền vào tiêu đề, bạn cần tạo bộ mô tả dữ liệu phần bằng cách sử dụng hàm elf_newdata:
Elf_Data * elf_newdata (Elf_Scn * scn);
  • scn là con trỏ mới có được đến phần mới.
Hàm trả về 0 khi bị lỗi hoặc một con trỏ đến cấu trúc Elf_Data để điền vào:
typedef struct (void * d_buf; Elf_Type d_type; size_t d_size; off_t d_off; size_t d_align; unsigned d_version;) Elf_Data;
  • d_buf - con trỏ đến dữ liệu được ghi vào phần
  • d_type - kiểu dữ liệu, ELF_T_BYTE phù hợp với chúng tôi ở mọi nơi
  • d_size - kích thước dữ liệu
  • d_off - phần bù đắp, được đặt thành 0
  • d_align - căn chỉnh, có thể được đặt thành 1 - không căn chỉnh
  • d_version - phiên bản, phải được đặt thành EV_CURRENT
Phần đặc biệt
Vì mục đích của chúng tôi, chúng tôi sẽ cần tạo tập hợp các phần cần thiết tối thiểu:
  • .text - phần có mã chương trình
  • .symtab - bảng ký hiệu của tệp
  • .strtab - một bảng chuỗi chứa các tên ký hiệu từ phần .symtab, vì phần sau không lưu trữ các tên đó mà là các chỉ số của chúng
  • .shstrtab - bảng chuỗi chứa tên phần
Tất cả các phần được tạo như mô tả trong phần trước, nhưng mỗi phần đặc biệt có những đặc điểm riêng.
Section.text
Phần này chứa mã thực thi, vì vậy bạn cần đặt sh_type thành SHT_PROGBITS, sh_flags thành SHF_EXECINSTR + SHF_ALLOC, sh_addr thành địa chỉ nơi mã này sẽ được tải
Section.symtab
Phần này chứa mô tả về tất cả các ký hiệu (chức năng) của chương trình và các tệp mà chúng được mô tả trong đó. Nó bao gồm các phần tử sau, mỗi phần tử dài 16 byte:
typedef struct (Elf32_Word st_name; Elf32_Addr st_value; Elf32_Word st_size; unsigned char st_info; unsigned char st_other; Elf32_Half st_shndx;) Elf32_Sym;
  • st_name - tên ký hiệu (chỉ mục trong string table.strtab)
  • st_value - giá trị (địa chỉ đầu vào cho một hàm hoặc 0 cho một tệp). Vì Cortex-M3 có tập lệnh Thumb-2 nên địa chỉ này phải là số lẻ (địa chỉ thực + 1)
  • st_size - độ dài mã hàm (0 cho tệp)
  • st_info - loại ký hiệu và phạm vi của nó. Có một macro để xác định giá trị của trường này
    #define ELF32_ST_INFO (b, t) (((b)<<4)+((t)&0xf))
    trong đó b là phạm vi và t là kiểu ký tự
    Phạm vi có thể là STB_LOCAL (biểu tượng không hiển thị trong các tệp đối tượng khác) hoặc STB_GLOBAL (hiển thị). Để đơn giản hóa, chúng tôi sử dụng STB_GLOBAL.
    Loại ký hiệu - STT_FUNC cho hàm, STT_FILE cho tệp
  • st_other - đặt thành 0
  • st_shndx - chỉ mục của phần mà ký hiệu được xác định (phần index.text) hoặc SHN_ABS cho tệp.
    Chỉ mục phần bằng tay cầm scn của nó có thể được xác định bằng cách sử dụng elf_ndxscn:
    size_t elf_ndxscn (Elf_Scn * scn);

Phần này được tạo theo cách thông thường, chỉ sh_type cần được đặt thành SHT_SYMTAB và chỉ mục section.strtab cần được ghi vào trường sh_link, để các phần này được liên kết với nhau.
Section.strtab
Phần này chứa tên của tất cả các ký hiệu từ phần .symtab. Được tạo giống như một phần thông thường, nhưng sh_type cần được đặt thành SHT_STRTAB, sh_flags thành SHF_STRINGS, vì vậy phần này trở thành một bảng chuỗi.
Dữ liệu cho phần có thể được thu thập khi chuyển qua văn bản nguồn vào một mảng, con trỏ tới đó sau đó được ghi vào bộ mô tả dữ liệu phần (d_buf).
Section.shstrtab
Phần - một bảng chuỗi, chứa các tiêu đề của tất cả các phần của tệp, bao gồm cả tiêu đề của chính nó. Nó được tạo theo cách tương tự như phần .strtab. Sau khi tạo, chỉ mục của nó phải được ghi trong trường e_shstrndx của tiêu đề tệp.
Bảng chuỗi
Bảng chuỗi chứa các chuỗi liên tiếp kết thúc bằng byte rỗng, byte đầu tiên trong bảng đó cũng phải bằng 0. Chỉ số hàng trong bảng chỉ đơn giản là một khoảng chênh lệch tính bằng byte kể từ đầu bảng, vì vậy chuỗi đầu tiên "tên" có chỉ mục 1, chuỗi tiếp theo "var" có chỉ số 6.
Chỉ số 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 \ 0 n a m e \ 0 v a r \ 0
Viết tệp
Vì vậy, các tiêu đề và phần đã được hình thành, bây giờ chúng cần được ghi vào một tệp và hoàn thành với bản thân. Việc ghi được thực hiện bởi hàm elf_update:
off_t elf_update (Elf * elf, Elf_Cmd cmd);
  • elf - tay cầm
  • cmd - lệnh, phải bằng ELF_C_WRITE để viết.
Hàm trả về -1 khi bị lỗi. Văn bản lỗi có thể nhận được bằng cách gọi hàm elf_errmsg (-1), hàm này sẽ trả về một con trỏ đến dòng có lỗi.
Chúng tôi kết thúc quá trình làm việc với thư viện bằng hàm elf_end, hàm này chúng tôi chuyển bộ mô tả của mình vào. Nó vẫn chỉ để đóng tệp đã mở trước đó.
Tuy nhiên, tệp được tạo của chúng tôi không chứa thông tin gỡ lỗi, chúng tôi sẽ thêm thông tin này trong phần tiếp theo.

Tạo DWARF

Chúng tôi sẽ tạo thông tin gỡ lỗi bằng cách sử dụng thư viện, đi kèm với tệp pdf có tài liệu (libdwarf2p.1.pdf - Giao diện Thư viện Producer cho DWARF).
Tạo thông tin gỡ lỗi bao gồm các bước sau:
  1. Tạo các nút (DIE - Mục nhập thông tin gỡ lỗi)
  2. Tạo thuộc tính nút
  3. Tạo các loại dữ liệu
  4. Tạo các thủ tục (hàm)
Xem xét các bước chi tiết hơn
Khởi tạo trình tạo libdwarf
Chúng tôi sẽ tạo thông tin gỡ lỗi tại thời điểm biên dịch cùng lúc với việc tạo các ký hiệu trong phần .symtab, vì vậy việc khởi tạo thư viện nên được thực hiện sau khi khởi tạo libelf, tạo tiêu đề ELF và tiêu đề chương trình, trước khi tạo các phần.
Để khởi tạo, chúng ta sẽ sử dụng hàm lùn_producer_init_c. Thư viện có một số hàm khởi tạo khác (lùn_producer_init, lùn_producer_init_b), các hàm này khác nhau ở một số sắc thái được mô tả trong tài liệu. Về nguyên tắc, bất kỳ cái nào trong số chúng đều có thể được sử dụng.

Dwarf_P_Debugwarf_producer_init_c (Dwarf_Unsigned cờ, Dwarf_Callback_Func_c func, Dwarf_Handler errhand, Dwarf_Ptr errarg, void * user_data, Dwarf_Error * error)

  • cờ - sự kết hợp của "hoặc" một số hằng số xác định một số tham số, ví dụ: độ sâu bit của thông tin, thứ tự byte (little-endian, big-endian), định dạng chuyển vị trí, trong đó chúng tôi chắc chắn cần DW_DLC_WRITE và DW_DLC_SYMBOLIC_RELOCATIONS
  • func - hàm gọi lại sẽ được gọi khi tạo các phần ELF với thông tin gỡ lỗi. Để biết thêm chi tiết, hãy xem phần "Tạo phần với thông tin gỡ lỗi" bên dưới.
  • errhand là một con trỏ đến một hàm được gọi khi xảy ra lỗi. Có thể vượt qua 0
  • errarg - dữ liệu sẽ được chuyển cho hàm errhand, có thể được đặt thành 0
  • user_data - dữ liệu sẽ được chuyển đến hàm func, có thể được đặt thành 0
  • lỗi - mã lỗi trả về
Hàm trả về Dwarf_P_Debug - một bộ mô tả được sử dụng trong tất cả các hàm tiếp theo hoặc -1 trong trường hợp có lỗi, trong khi lỗi sẽ chứa mã lỗi (bạn có thể lấy văn bản của thông báo lỗi bằng mã của nó bằng cách sử dụng hàm lùn_errmsg, chuyển mã này với nó)
Tạo nút (DIE - Mục nhập thông tin gỡ lỗi)
Như đã mô tả ở trên, thông tin gỡ lỗi tạo thành một cấu trúc cây. Để tạo một nút của cây này, bạn cần:
  • tạo nó bằng hàm lùn_new_die
  • thêm các thuộc tính vào nó (mỗi loại thuộc tính được thêm vào bởi chức năng riêng của nó, sẽ được mô tả sau)
Nút được tạo bằng cách sử dụng hàm lùn_new_die:
Dwarf_P_Die lùn_new_die (Dwarf_P_Debug dbg, Dwarf_Tag new_tag, Dwarf_P_Die cha mẹ, Dwarf_P_Die con, Dwarf_P_Die left_sibling, Dwarf_P_Die right_sibling, Dwarf_Error * error)
  • new_tag - thẻ nút (loại) - hằng số DW_TAG_xxxx, có thể được tìm thấy trong tệp libdwarf.h
  • cha, con, left_sibling, right_sibling - tương ứng là cha, con, hàng xóm bên trái và bên phải của nút. Không nhất thiết phải chỉ định tất cả các tham số này, chỉ cần chỉ định một là đủ, thay vì phần còn lại đặt 0. Nếu tất cả các tham số bằng 0, nút sẽ là gốc hoặc bị cô lập
  • error - sẽ chứa mã lỗi khi nó xảy ra
Hàm trả về DW_DLV_BADADDR khi thất bại hoặc xử lý nút Dwarf_P_Die khi thành công
Tạo thuộc tính nút
Có cả một họ các hàm lùn_add_AT_xxxx để tạo các thuộc tính nút. Đôi khi việc xác định hàm nào cần tạo thuộc tính cần thiết cũng có vấn đề, vì vậy tôi thậm chí đã đào sâu vào mã nguồn của thư viện vài lần. Một số chức năng sẽ được mô tả ở đây, một số chức năng bên dưới - trong các phần có liên quan. Tất cả chúng đều nhận một tham số ownerdie, một xử lý cho nút mà thuộc tính sẽ được thêm vào và trả về mã lỗi trong tham số lỗi.
Hàm lùn_add_AT_name thêm thuộc tính "tên" (DW_AT_name) vào một nút. Hầu hết các nút phải có tên (ví dụ: thủ tục, biến, hằng số), một số có thể không có tên (ví dụ: Đơn vị biên dịch)
Dwarf_P_Attributewarf_add_AT_name (Dwarf_P_Die ownerdie, char * name, Dwarf_Error * error)
  • tên - giá trị thực của thuộc tính (tên nút)

Các hàm lùn_add_AT_signed_const, lùn_add_AT_unsigned_const thêm thuộc tính được chỉ định và giá trị có dấu (không dấu) của nó vào nút. Các thuộc tính có dấu và không dấu được sử dụng để đặt các giá trị không đổi, kích thước, số dòng, v.v. Định dạng hàm:
Dwarf_P_Attributewarf_add_AT_ (un) sign_const (Dwarf_P_Debug dbg, Dwarf_P_Die ownerdie, Dwarf_Half attr, Dwarf_Signed value, Dwarf_Error * error)
  • dbg - Bộ mô tả Dwarf_P_Debug nhận được trong quá trình khởi tạo thư viện
  • attr - thuộc tính có giá trị được đặt - hằng số DW_AT_xxxx, có thể được tìm thấy trong tệp libdwarf.h
  • value - giá trị của thuộc tính
Trả lại DW_DLV_BADADDR nếu bị lỗi hoặc xử lý thuộc tính thành công.
Tạo một đơn vị biên dịch
Bất kỳ cây nào cũng phải có gốc - trong trường hợp của chúng tôi, nó là một đơn vị biên dịch chứa thông tin về chương trình (ví dụ: tên của tệp chính, ngôn ngữ lập trình được sử dụng, tên của trình biên dịch, độ nhạy của các ký tự (biến, các chức năng) đối với trường hợp, chức năng chính của chương trình, địa chỉ bắt đầu, v.v.). v.v.). Về nguyên tắc, không có thuộc tính nào được yêu cầu. Ví dụ: hãy tạo thông tin về tệp chính và trình biên dịch.
Thông tin về tệp chính
Thuộc tính "name" (DW_AT_name) được sử dụng để lưu trữ thông tin về tệp chính, sử dụng hàm lùn_add_AT_name như trong phần "Tạo thuộc tính nút".
Thông tin trình biên dịch
Chúng tôi sử dụng hàm lùn_add_AT_producer:
Dwarf_P_Attributewarf_add_AT_name (Dwarf_P_Die ownerdie, char * producer_string, Dwarf_Error * error)
  • producer_string - chuỗi có văn bản thông tin
Trả về DW_DLV_BADADDR nếu có lỗi hoặc xử lý thuộc tính thành công.
Tạo một mục nhập thông tin chung
Thông thường, khi một hàm (chương trình con) được gọi, các tham số và địa chỉ trả về của nó được đặt trên ngăn xếp (mặc dù mỗi trình biên dịch có thể thực hiện việc này theo cách riêng của nó), tất cả điều này được gọi là Call Frame. Trình gỡ lỗi cần thông tin về định dạng khung để xác định chính xác địa chỉ trả về từ hàm và xây dựng dấu vết - chuỗi lệnh gọi hàm dẫn chúng ta đến hàm hiện tại và các tham số của các hàm này. Nó cũng thường chỉ định các thanh ghi bộ xử lý được lưu trữ trên ngăn xếp. Mã dự trữ không gian trên ngăn xếp và lưu các thanh ghi của bộ xử lý được gọi là phần mở đầu hàm, mã khôi phục các thanh ghi và ngăn xếp được gọi là phần kết.
Thông tin này phụ thuộc nhiều vào trình biên dịch. Ví dụ, phần mở đầu và phần kết không nhất thiết phải ở đầu và cuối của hàm; đôi khi một khung được sử dụng, đôi khi không; thanh ghi bộ xử lý có thể được lưu trữ trong các thanh ghi khác, v.v.
Vì vậy, trình gỡ lỗi cần biết cách các thanh ghi bộ xử lý thay đổi giá trị của chúng và nơi chúng sẽ được lưu trữ khi nhập thủ tục. Thông tin này được gọi là Call Frame Information - thông tin định dạng khung. Ví dụ: đối với mỗi địa chỉ trong chương trình (chứa mã), địa chỉ của khung trong bộ nhớ (Địa chỉ Khung Canonical - CFA) và thông tin về các thanh ghi bộ xử lý, bạn có thể chỉ định rằng:
  • trường hợp không được bảo quản trong thủ tục
  • thanh ghi không thay đổi giá trị của nó trong thủ tục
  • thanh ghi được lưu trữ trên ngăn xếp tại địa chỉ CFA + n
  • sổ đăng ký được lưu trữ trong một sổ đăng ký khác
  • thanh ghi được lưu trữ trong bộ nhớ tại một số địa chỉ, có thể được tính toán theo một cách không rõ ràng
  • vân vân.
Vì thông tin phải được chỉ định cho từng địa chỉ trong mã, nó rất lớn và được lưu trữ dưới dạng nén trong phần .debug_frame. Vì nó thay đổi ít từ địa chỉ này sang địa chỉ khác, chỉ những thay đổi của nó được mã hóa dưới dạng hướng dẫn DW_CFA_хххх. Mỗi lệnh chỉ ra một thay đổi, ví dụ:
  • DW_CFA_set_loc - trỏ đến địa chỉ hiện tại trong chương trình
  • DW_CFA_advance_loc - Tăng con trỏ lên một số byte
  • DW_CFA_def_cfa - chỉ định địa chỉ của khung ngăn xếp (hằng số)
  • DW_CFA_def_cfa_register - chỉ định địa chỉ của khung ngăn xếp (lấy từ thanh ghi bộ xử lý)
  • DW_CFA_def_cfa_expression - chỉ định cách tính địa chỉ khung ngăn xếp
  • DW_CFA_same_value - chỉ ra rằng trường hợp không được thay đổi
  • DW_CFA_register - chỉ ra rằng đăng ký được lưu trữ trong một đăng ký khác
  • vân vân.
Các phần tử của phần .debug_frame là các mục nhập có thể có hai loại: Mục nhập thông tin chung (CIE) và mục nhập mô tả khung (FDE). CIE chứa thông tin chung cho nhiều mục FDE, nói một cách đại khái nó mô tả một loại thủ tục cụ thể. FDE mô tả từng thủ tục cụ thể. Khi nhập một thủ tục, trình gỡ lỗi trước tiên thực hiện các hướng dẫn từ CIE và sau đó từ FDE.
Trình biên dịch của tôi tạo ra các thủ tục trong đó CFA nằm trong thanh ghi sp (r13). Hãy tạo CIE cho tất cả các thủ tục. Có một hàm lùn_add_frame_cie cho điều này:
Dwarf_Unsigned lùn_add_frame_cie (Dwarf_P_Debug dbg, char * augmenter, Dwarf_Small code_align, Dwarf_Small data_align, Dwarf_Small ret_addr_reg, Dwarf_Ptr init_bytes, Dwarf_Unsigned error, Dwarf_UnsignedEnit_bytes);
  • augmenter - chuỗi được mã hóa UTF-8, sự hiện diện của chuỗi này chỉ ra rằng có thêm thông tin về nền tảng cụ thể cho CIE hoặc FDE. Đặt một dòng trống
  • code_align - căn chỉnh mã theo byte (chúng tôi có 2)
  • data_align - căn chỉnh dữ liệu trong khung (set -4, có nghĩa là tất cả các tham số chiếm 4 byte trên ngăn xếp và nó tăng dần trong bộ nhớ)
  • ret_addr_reg - đăng ký chứa địa chỉ trả về từ thủ tục (chúng tôi có 14)
  • init_bytes - mảng chứa các lệnh DW_CFA_xxxx. Thật không may, không có cách nào thuận tiện để tạo mảng này. Bạn có thể tạo nó theo cách thủ công hoặc xem nó trong tệp elf được tạo bởi trình biên dịch C, mà tôi đã làm. Đối với trường hợp của tôi, nó chứa 3 byte: 0x0C, 0x0D, 0, viết tắt của DW_CFA_def_cfa: r13 ofs 0 (CFA nằm trong thanh ghi r13, độ lệch là 0)
  • init_bytes_len - độ dài của mảng init_bytes
Hàm trả về DW_DLV_NOCOUNT khi bị lỗi hoặc xử lý CIE nên được sử dụng khi tạo FDE cho mỗi thủ tục, chúng ta sẽ thảo luận sau trong phần "Tạo thủ tục FDE".
Tạo các loại dữ liệu
Trước khi tạo các thủ tục và biến, trước tiên bạn phải tạo các nút tương ứng với các kiểu dữ liệu. Có rất nhiều kiểu dữ liệu, nhưng chúng đều dựa trên các kiểu cơ bản (kiểu cơ bản như int, double, v.v.), các kiểu khác được xây dựng từ những kiểu cơ bản.
Loại cơ sở là nút có thẻ DW_TAG_base_type. Nó phải có các thuộc tính sau:
  • "tên" (DW_AT_name)
  • "encoding" (DW_AT_encoding) - có nghĩa là chính xác dữ liệu mà kiểu cơ sở này mô tả (ví dụ: DW_ATE_boolean - boolean, DW_ATE_float - dấu phẩy động, DW_ATE_signed - số nguyên có dấu, DW_ATE_unsigned - số nguyên không dấu, v.v.)
  • "size" (DW_AT_byte_size - kích thước tính bằng byte hoặc DW_AT_bit_size - kích thước tính bằng bit)
Nút cũng có thể chứa các thuộc tính tùy chọn khác.
Ví dụ: để tạo kiểu cơ sở có dấu số nguyên 32 bit "int", chúng ta sẽ cần tạo một nút với thẻ DW_TAG_base_type và đặt các thuộc tính DW_AT_name - "int", DW_AT_encoding - DW_ATE_signed, DW_AT_byte_size - 4.
Sau khi tạo các loại cơ sở, bạn có thể tạo các dẫn xuất từ ​​chúng. Các nút như vậy phải chứa thuộc tính DW_AT_type - một tham chiếu đến kiểu cơ sở của chúng. Ví dụ: một con trỏ tới int - một nút có thẻ DW_TAG_pointer_type phải chứa tham chiếu đến kiểu "int" đã tạo trước đó trong thuộc tính DW_AT_type.
Một thuộc tính có tham chiếu đến một nút khác được tạo bởi hàm lùn_add_AT_reference:
Dwarf_P_Attributewarf_add_AT_reference (Dwarf_P_Debug dbg, Dwarf_P_Die ownerdie, Dwarf_Half attr, Dwarf_P_Die otherdie, Dwarf_Error * error)
  • attr - thuộc tính, trong trường hợp này là DW_AT_type
  • otherdie - một xử lý đối với nút của loại đang được tham chiếu
Tạo thủ tục
Để tạo thủ tục, tôi cần giải thích thêm một loại thông tin gỡ lỗi - Thông tin số dòng. Nó đóng vai trò ánh xạ từng lệnh máy tới một dòng cụ thể của mã nguồn và cũng để cho phép gỡ lỗi từng dòng của chương trình. Thông tin này được lưu trữ trong phần .debug_line. Nếu chúng ta có đủ không gian, thì nó sẽ được lưu trữ dưới dạng ma trận, một hàng cho mỗi lệnh với các cột như sau:
  • tên tệp nguồn
  • số dòng trong tệp này
  • số cột trong tệp
  • liệu lệnh có phải là phần bắt đầu của một câu lệnh hay một khối câu lệnh hay không
  • vân vân.
Một ma trận như vậy sẽ rất lớn, vì vậy nó phải được nén. Thứ nhất, các dòng trùng lặp bị loại bỏ, và thứ hai, bản thân các dòng không được lưu mà chỉ thay đổi chúng. Những thay đổi này trông giống như các lệnh cho một máy trạng thái hữu hạn và bản thân thông tin đã được coi là một chương trình sẽ được máy này “thực thi”. Các lệnh của chương trình này trông giống như sau: DW_LNS_advance_pc - nâng cao bộ đếm chương trình đến một số địa chỉ, DW_LNS_set_file - đặt tệp trong đó thủ tục được xác định, DW_LNS_const_add_pc - nâng bộ đếm chương trình lên một vài byte, v.v.
Rất khó để tạo thông tin này ở mức thấp như vậy, vì vậy thư viện libdwarf cung cấp một số chức năng để làm cho nhiệm vụ này dễ dàng hơn.
Rất tốn kém để lưu trữ tên tệp cho mỗi lệnh, vì vậy thay vì tên, chỉ mục của nó được lưu trữ trong một bảng đặc biệt. Để tạo chỉ mục tệp, hãy sử dụng hàm lùn_add_file_decl:
Dwarf_Unsigned lùn_add_file_decl (Dwarf_P_Debug dbg, char * name, Dwarf_Unsigned dir_idx, Dwarf_Unsigned time_mod, Dwarf_Unsigned length, Dwarf_Error * error)
  • name - tên của tệp
  • dir_idx - chỉ mục của thư mục chứa tệp. Chỉ mục có thể được lấy bằng cách sử dụng hàm lùn_add_directory_decl. Nếu các đường dẫn đầy đủ được sử dụng, bạn có thể đặt 0 làm chỉ mục thư mục và hoàn toàn không sử dụngwarf_add_directory_decl
  • time_mod - thời gian sửa đổi tệp, có thể được bỏ qua (0)
  • chiều dài - kích thước tệp, cũng là tùy chọn (0)
Hàm sẽ trả về chỉ mục của tệp hoặc DW_DLV_NOCOUNT khi bị lỗi.
Để tạo thông tin về số dòng, có ba hàm lùn_add_line_entry_b, lùn_lne_set_address, lùn_lne_end_sequence, chúng ta sẽ xem xét bên dưới.
Tạo thông tin gỡ lỗi cho một thủ tục trải qua một số bước:
  • tạo một biểu tượng thủ tục trong phần .symtab
  • tạo một nút thủ tục với các thuộc tính
  • tạo một thủ tục FDE
  • tạo các tham số thủ tục
  • tạo thông tin số dòng
Tạo một biểu tượng thủ tục
Biểu tượng thủ tục được tạo như mô tả ở trên trong phần "Section.symtab". Trong đó, các ký hiệu của các thủ tục được xen kẽ với các ký hiệu của các tập tin chứa mã nguồn của các thủ tục này. Đầu tiên chúng ta tạo ký hiệu tệp, sau đó là các thủ tục. Điều này làm cho tệp hiện hành và nếu quy trình tiếp theo nằm trong tệp hiện tại, thì biểu tượng tệp không cần phải được tạo lại.
Tạo một nút thủ tục với các thuộc tính
Đầu tiên, chúng tôi tạo một nút bằng cách sử dụng hàm lùn_new_die (xem phần "Tạo nút"), chỉ định thẻ DW_TAG_subprogram làm thẻ và Đơn vị biên dịch (nếu đây là quy trình toàn cục) hoặc DIE tương ứng (nếu cục bộ) làm cha mẹ. Tiếp theo, chúng tôi tạo các thuộc tính:
  • tên thủ tục (hàm lùn_add_AT_name, xem "Tạo thuộc tính nút")
  • số dòng trong tệp nơi mã thủ tục bắt đầu (thuộc tính DW_AT_decl_line), hàm lùn_add_AT_unsigned_const (xem "Tạo thuộc tính nút")
  • địa chỉ bắt đầu thủ tục (thuộc tính DW_AT_low_pc), hàm lùn_add_AT_targ_address, xem bên dưới
  • địa chỉ cuối của thủ tục (thuộc tính DW_AT_high_pc), hàm lùn_add_AT_targ_address, xem bên dưới
  • loại kết quả được trả về bởi thủ tục (thuộc tính DW_AT_type là một liên kết đến một loại đã tạo trước đó, xem phần "Tạo kiểu dữ liệu"). Nếu thủ tục không trả về bất kỳ thứ gì, thì thuộc tính này không cần phải được tạo.
Các thuộc tính DW_AT_low_pc và DW_AT_high_pc phải được tạo bằng hàm lùn_add_AT_targ_address_b được thiết kế đặc biệt cho điều này:
Dwarf_P_Attributewarf_add_AT_targ_address_b (Dwarf_P_Debug dbg, Dwarf_P_Die ownerdie, Dwarf_Half attr, Dwarf_Unsigned pc_value, Dwarf_Unsigned sym_index, Dwarf_Error * error)
  • attr - thuộc tính (DW_AT_low_pc hoặc DW_AT_high_pc)
  • pc_value - giá trị địa chỉ
  • sym_index - chỉ mục của ký hiệu thủ tục trong bảng .symtab. Tùy chọn, bạn có thể vượt qua 0
Hàm sẽ trả về DW_DLV_BADADDR khi bị lỗi.
Tạo một thủ tục FDE
Như đã đề cập ở trên trong phần “Tạo mục nhập thông tin chung”, đối với mỗi thủ tục, bạn cần tạo bộ mô tả khung, diễn ra trong một số giai đoạn:
  • tạo FDE mới (xem Tạo mục nhập thông tin chung)
  • đính kèm FDE đã tạo vào danh sách chung
  • thêm hướng dẫn vào FDE đã tạo
Bạn có thể tạo một FDE mới bằng hàm lùn_new_fde:
Dwarf_P_Fdewarf_new_fde (Dwarf_P_Debug dbg, Dwarf_Error * error)
Hàm sẽ trả về một xử lý cho FDE hoặc DW_DLV_BADADDR mới khi bị lỗi.
Bạn có thể thêm một FDE mới vào danh sách với lùn_add_frame_fde:
Dwarf_Unsigned lùn_add_frame_fde (Dwarf_P_Debug dbg, Dwarf_P_Fde fde, Dwarf_P_Die die, Dwarf_Unsigned cie, Dwarf_Addr Virt_addr, Dwarf_Unsigned code_id_len, Dwarf_Unsigned error * symf_idError
  • fde - tay cầm vừa nhận được
  • die - Thủ tục DIE (xem Tạo nút thủ tục với các thuộc tính)
  • cie - Bộ mô tả CIE (xem Tạo mục nhập thông tin chung)
  • Virt_addr - địa chỉ bắt đầu của quy trình của chúng tôi
  • code_len - độ dài thủ tục tính bằng byte
Hàm sẽ trả về DW_DLV_NOCOUNT khi bị lỗi.
Sau tất cả, chúng ta có thể thêm hướng dẫn DW_CFA_хххх vào FDE của chúng ta. Điều này được thực hiện với các hàm lùn_add_fde_inst và lùn_fde_cfa_offset. Đầu tiên thêm hướng dẫn đã cho vào danh sách:
Dwarf_P_Fdewarf_add_fde_inst (Dwarf_P_Fde fde, Dwarf_Small op, Dwarf_Unsigned val1, Dwarf_Unsigned val2, Dwarf_Error * error)
  • op - mã hướng dẫn (DW_CFA_хххх)
  • val1, val2 - tham số lệnh (khác nhau đối với từng lệnh, xem Tiêu chuẩn, phần 6.4.2 Hướng dẫn Khung cuộc gọi)
Hàm lùn_fde_cfa_offset thêm lệnh DW_CFA_offset:
Dwarf_P_Fdewarf_fde_cfa_offset (Dwarf_P_Fde fde, Dwarf_Unsigned reg, Dwarf_Signed offset, Dwarf_Error * error)
  • fde - xử lý FDE đã tạo
  • reg - thanh ghi được ghi vào khung
  • offset - độ lệch của nó trong khung (không tính bằng byte, mà trong các phần tử khung, hãy xem Tạo mục nhập thông tin chung, data_align)
Ví dụ, trình biên dịch tạo ra một thủ tục mà prolog lưu thanh ghi lr (r14) vào khung ngăn xếp. Trước hết, bạn cần thêm lệnh DW_CFA_advance_loc với tham số đầu tiên bằng 1, có nghĩa là phần trước của thanh ghi pc thêm 2 byte (xem phần Tạo mục nhập thông tin chung, code_align), sau đó thêm DW_CFA_def_cfa_offset với tham số 4 (cài đặt độ lệch dữ liệu trong khung là 4 byte) và gọi hàm lùn_fde_cfa_offset với tham số reg = 14 offset = 1, nghĩa là ghi thanh ghi r14 vào khung với khoảng cách -4 byte từ CFA.
Tạo các tham số thủ tục
Tạo tham số thủ tục tương tự như tạo biến thông thường, xem "Tạo biến và hằng số"
Tạo thông tin số dòng
Thông tin này được tạo như thế này:
  • khi bắt đầu thủ tục, chúng ta bắt đầu khối lệnh bằng hàm lùn_lne_set_address
  • đối với mỗi dòng mã (hoặc lệnh máy), chúng tôi tạo thông tin về mã nguồn (lùn_add_line_entry)
  • ở cuối quy trình, chúng tôi hoàn thành khối hướng dẫn bằng hàm lùn_lne_end_sequence
Hàm lùn_lne_set_address đặt địa chỉ nơi bắt đầu khối lệnh:
Dwarf_Unsigned lùn_lne_set_address (Dwarf_P_Debug dbg, Dwarf_Addr off, Dwarf_Unsigned symidx, Dwarf_Error * error)
  • off - địa chỉ thủ tục (địa chỉ của lệnh máy đầu tiên)
  • sym_idx - chỉ mục biểu tượng (tùy chọn, bạn có thể chỉ định 0)

Hàm lùn_add_line_entry_b thêm thông tin về các dòng mã nguồn vào phần .debug_line. Tôi gọi chức năng này cho mọi lệnh máy:
Dwarf_Unsigned dwarf_add_line_entry_b(Dwarf_P_Debug dbg, Dwarf_Unsigned file_index, Dwarf_Addr code_offset, Dwarf_Unsigned lineno, Dwarf_Signed column_number, Dwarf_Bool is_source_stmt_begin, Dwarf_Bool is_basic_block_begin, Dwarf_Bool is_epilogue_begin, Dwarf_Bool is_prologue_end, Dwarf_Unsigned isa, Dwarf_Unsigned discriminator, Dwarf_Error *error)
  • file_index - chỉ mục của tệp mã nguồn được lấy trước đó bởi hàm lùn_add_file_decl (xem "Quy trình tạo")
  • code_offset - địa chỉ của lệnh máy hiện tại
  • lineno - số dòng trong tệp mã nguồn
  • column_number - số cột trong tệp mã nguồn
  • is_source_stmt_begin - 1 nếu lệnh hiện tại là lệnh đầu tiên trong mã trong dòng lineno (tôi luôn sử dụng 1)
  • is_basic_block_begin - 1 nếu lệnh hiện tại là lệnh đầu tiên trong khối câu lệnh (tôi luôn sử dụng 0)
  • is_epilogue_begin - 1 nếu lệnh hiện tại là lệnh đầu tiên trong phần kết của thủ tục (không cần thiết, tôi luôn có 0)
  • is_prologue_end - 1 nếu lệnh hiện tại là lệnh cuối cùng trong phần mở đầu của thủ tục (bắt buộc!)
  • isa - kiến ​​trúc tập lệnh (lệnh tập hợp kiến ​​trúc). Đảm bảo chỉ định DW_ISA_ARM_thumb cho ARM Cortex M3!
  • người phân biệt đối xử. Một vị trí (tệp, dòng, cột) của mã nguồn có thể tương ứng với các lệnh máy khác nhau. Trong trường hợp này, các dấu hiệu phân biệt khác nhau phải được thiết lập cho các bộ hướng dẫn như vậy. Nếu không có những trường hợp như vậy, nó phải là 0
Hàm trả về 0 (thành công) hoặc DW_DLV_NOCOUNT (lỗi).
Cuối cùng, hàm lùn_lne_end_sequence kết thúc thủ tục:
Dwarf_Unsignedwarf_lne_end_sequence (Dwarf_P_Debug dbg, Dwarf_Addr address; Dwarf_Error * error)
  • địa chỉ - địa chỉ của lệnh máy hiện tại
Trả về 0 (thành công) hoặc DW_DLV_NOCOUNT (lỗi).
Điều này hoàn tất việc tạo thủ tục.
Tạo biến và hằng số
Nói chung, các biến khá đơn giản. Chúng có tên, vị trí bộ nhớ (hoặc thanh ghi bộ xử lý) nơi chứa dữ liệu của chúng và loại dữ liệu đó. Nếu biến là toàn cục - cha của nó phải là Đơn vị biên dịch, nếu cục bộ - là nút tương ứng (điều này đặc biệt đúng với các tham số thủ tục, chúng phải có chính thủ tục làm cha). Bạn cũng có thể chỉ định tệp, dòng và cột mà khai báo biến nằm ở đâu.
Trong trường hợp đơn giản nhất, giá trị của biến được đặt tại một địa chỉ cố định nào đó, nhưng nhiều biến được tạo động khi nhập thủ tục trên ngăn xếp hoặc thanh ghi, đôi khi việc tính toán địa chỉ của giá trị có thể khá không nhỏ. Tiêu chuẩn cung cấp một cơ chế để mô tả vị trí của giá trị của một biến - các biểu thức vị trí. Biểu thức địa chỉ là một tập hợp các lệnh (hằng số DW_OP_xxxx) cho một máy ngăn xếp tương tự, trên thực tế, nó là một ngôn ngữ riêng biệt với các nhánh, thủ tục và phép toán số học. Chúng tôi sẽ không đánh giá toàn bộ ngôn ngữ này, chúng tôi thực sự sẽ chỉ quan tâm đến một số hướng dẫn:
  • DW_OP_addr - chỉ định địa chỉ của một biến
  • DW_OP_fbreg - Chỉ ra độ lệch của biến từ thanh ghi cơ sở (thường là con trỏ ngăn xếp)
  • DW_OP_reg0 ... DW_OP_reg31 - chỉ ra rằng biến được lưu trữ trong thanh ghi tương ứng
Để tạo một biểu thức đích, trước tiên bạn phải tạo một biểu thức trống (lùn_new_expr), thêm hướng dẫn vào biểu thức đó (lùn_add_expr_addr, lùn_add_expr_gen, v.v.) và thêm biểu thức đó vào nút dưới dạng giá trị của thuộc tính DW_AT_location (biểu thức lùn_add_AT_location_expression).
Hàm để tạo một biểu thức địa chỉ trống sẽ trả về xử lý của nó hoặc 0 khi bị lỗi:
Dwarf_Exprwarf_new_expr (Dwarf_P_Debug dbg, Dwarf_Error * error)
Để thêm hướng dẫn vào một biểu thức, hãy sử dụng hàm lùn_add_expr_gen:
Dwarf_Unsigned lùn_add_expr_gen (Dwarf_P_Expr expr, Dwarf_Small opcode, Dwarf_Unsigned val1, Dwarf_Unsigned val2, Dwarf_Error * error)
  • opcode - mã hoạt động, hằng số DW_OP_хххх
  • val1, val2 - tham số lệnh (xem tiêu chuẩn)

Để đặt địa chỉ của một biến một cách rõ ràng, nên sử dụng hàm lùn_add_expr_addr thay vì hàm trước đó:
Dwarf_Unsigned lùn_add_expr_addr (Dwarf_P_Expr expr, Dwarf_Unsigned address, Dwarf_Signed sym_index, Dwarf_Error * error)
  • expr - xử lý biểu thức địa chỉ mà lệnh được thêm vào
  • địa chỉ - địa chỉ biến
  • sym_index - chỉ mục ký hiệu trong bảng .symtab. Tùy chọn, bạn có thể vượt qua 0
Hàm cũng trả về DW_DLV_NOCOUNT do lỗi.
Và cuối cùng, bạn có thể thêm biểu thức địa chỉ đã tạo vào nút bằng cách sử dụng hàm lùn_add_AT_location_expr:
Dwarf_P_Attributewarf_add_AT_location_expr (Dwarf_P_Debug dbg, Dwarf_P_Die ownerdie, Dwarf_Half attr, Dwarf_P_Expr loc_expr, Dwarf_Error * error)
  • ownerdie - nút mà biểu thức được thêm vào
  • attr - thuộc tính (trong trường hợp của chúng tôi là DW_AT_location)
  • loc_expr - xử lý biểu thức địa chỉ đã tạo trước đó
Hàm trả về lỗi xử lý thuộc tính hoặc DW_DLV_NOCOUNT.
Các biến (cũng như các tham số thủ tục) và hằng số là các nút thông thường có thẻ DW_TAG_variable, DW_TAG_formal_parameter và DW_TAG_const_type tương ứng. Họ cần các thuộc tính sau:
  • tên biến / hằng số (hàm lùn_add_AT_name, xem "Tạo thuộc tính nút")
  • số dòng trong tệp nơi biến được khai báo (thuộc tính DW_AT_decl_line), hàm lùn_add_AT_unsigned_const (xem "Tạo thuộc tính nút")
  • chỉ mục tên tệp (thuộc tính DW_AT_decl_file), hàm lùn_add_AT_unsigned_const (xem "Tạo thuộc tính nút")
  • kiểu dữ liệu biến / hằng số (thuộc tính DW_AT_type là một liên kết đến một kiểu đã tạo trước đó, hãy xem "Tạo kiểu dữ liệu")
  • biểu thức địa chỉ (xem ở trên) - cần thiết cho một biến hoặc tham số thủ tục
  • hoặc giá trị - cho một hằng số (thuộc tính DW_AT_const_value, xem "Tạo thuộc tính nút")
Tạo các phần với thông tin gỡ lỗi
Sau khi tạo tất cả các nút của cây thông tin gỡ lỗi, bạn có thể bắt đầu tạo các phần elf với nó. Điều này xảy ra trong hai giai đoạn:
  • trước tiên bạn cần gọi hàm lùn_transform_to_disk_form, hàm này sẽ gọi hàm chúng ta đã viết để tạo các phần elf mong muốn một lần cho mỗi phần
  • đối với mỗi phần, hàm lùn_get_section_bytes sẽ trả về dữ liệu cho chúng tôi, dữ liệu này sẽ cần được ghi vào phần tương ứng
Hàm số
lùn_transform_to_disk_form (Dwarf_P_Debug dbg, lỗi Dwarf_Error *)
chuyển đổi thông tin gỡ lỗi mà chúng tôi đã tạo sang định dạng nhị phân, nhưng không ghi bất kỳ thứ gì vào đĩa. Nó sẽ trả về số phần elf đã tạo hoặc DW_DLV_NOCOUNT khi bị lỗi. Trong trường hợp này, đối với mỗi phần, hàm gọi lại sẽ được gọi, mà chúng ta đã truyền khi khởi tạo thư viện cho hàm lùn_producer_init_c. Chúng ta phải tự viết hàm này. Đặc điểm kỹ thuật của nó là:
typedef int (* Dwarf_Callback_Func_c) (char * name, int size, Dwarf_Unsigned type, Dwarf_Unsigned flags, Dwarf_Unsigned link, Dwarf_Unsigned information, Dwarf_Unsigned * inherit_name_index, void * user_data, int * error)
  • name - tên của phần elf sẽ được tạo
  • kích thước - kích thước phần
  • type - loại phần
  • cờ - cờ phần
  • liên kết - trường liên kết phần
  • thông tin - trường thông tin phần
  • Tên_nghiệp_của_nghiệp - bạn cần trả về chỉ mục của phần với các vị trí (tuỳ chọn)
  • user_data - được chuyển cho chúng tôi theo cách giống như cách chúng tôi đặt nó trong hàm khởi tạo thư viện
  • lỗi - ở đây bạn có thể chuyển mã lỗi
Trong chức năng này, chúng ta phải:
  • tạo một phần mới (chức năng elf_newscn, xem phần Tạo phần)
  • tạo tiêu đề phần (function elf32_getshdr, ibid.)
  • điền vào nó một cách chính xác (xem sđd.). Điều này rất dễ dàng vì các trường tiêu đề phần tương ứng với các tham số hàm của chúng ta. Các trường bị thiếu sh_addr, sh_offset, sh_entsize sẽ được đặt thành 0 và sh_addralign thành 1
  • trả về chỉ mục của phần đã tạo (function elf_ndxscn, xem "Section.symtab") hoặc -1 khi bị lỗi (bằng cách đặt mã lỗi thành lỗi)
  • Ngoài ra, chúng tôi phải bỏ qua phần ".rel" (trong trường hợp của chúng tôi) bằng cách trả về 0 khi trả về từ hàm
Sau khi hoàn thành, hàm lùn_transform_to_disk_form sẽ trả về số phân vùng đã tạo. Chúng ta sẽ cần lặp lại từ 0 qua từng phần, làm theo các bước sau:
  • tạo dữ liệu để ghi vào phần bằng hàm lùn_get_section_bytes:
    Dwarf_Ptrwarf_get_section_bytes (Dwarf_P_Debug dbg, Dwarf_Signed lùn_section, Dwarf_Signed * elf_section_index, Dwarf_Unsigned * length, Dwarf_Error * error)
    • lùn_section - số phần. Phải nằm trong phạm vi 0..n, trong đó n là số được trả về cho chúng ta bởi hàm lùn_transform_to_disk_form
    • elf_section_index - trả về chỉ mục của phần để ghi dữ liệu vào
    • length - độ dài của dữ liệu này
    • lỗi - không được sử dụng
    Hàm trả về một con trỏ đến dữ liệu đã nhận hoặc 0 (nếu
    khi không còn phần nào để tạo)
  • tạo bộ mô tả dữ liệu cho phần hiện tại (hàm elf_newdata, xem phần Tạo phần) và điền vào nó (xem sđd.) bằng cách cài đặt:
    • d_buf - con trỏ đến dữ liệu chúng tôi nhận được từ hàm trước đó
    • d_size - kích thước của dữ liệu này (ibid.)
Kết thúc công việc với thư viện
Sau khi tạo các phần, bạn có thể hoàn tất quá trình làm việc với libdwarf bằng hàm lùn_producer_finish:
Dwarf_Unsigned lùn_producer_finish (Dwarf_P_Debug dbg, Dwarf_Error * lỗi)
Hàm trả về DW_DLV_NOCOUNT do lỗi.
Tôi lưu ý rằng việc ghi vào đĩa ở giai đoạn này không được thực hiện. Việc ghi phải được thực hiện bằng các chức năng trong phần "Tạo ELF - Viết tệp".

Sự kết luận

Đó là tất cả.
Tôi nhắc lại, việc tạo ra thông tin gỡ lỗi là một chủ đề rất rộng, và tôi không đụng đến nhiều chủ đề, chỉ mở ra bức màn. Những người mong muốn có thể đi sâu vào vô cùng.
Nếu bạn có câu hỏi, tôi sẽ cố gắng trả lời chúng.

Định dạng ELF

Định dạng ELF có một số loại tệp mà chúng tôi đã đặt tên khác nhau cho đến nay, chẳng hạn như tệp thực thi hoặc tệp đối tượng. Tuy nhiên, tiêu chuẩn ELF phân biệt giữa các loại sau:

1. Tệp đang được di chuyển(tệp có thể định vị lại) lưu trữ các hướng dẫn và dữ liệu có thể được liên kết với các tệp đối tượng khác. Kết quả của liên kết như vậy có thể là một tệp thực thi hoặc một tệp đối tượng được chia sẻ.

2. Tệp đối tượng được chia sẻ(tệp đối tượng được chia sẻ) cũng chứa hướng dẫn và dữ liệu, nhưng có thể được sử dụng theo hai cách. Trong trường hợp đầu tiên, nó có thể được liên kết với các tệp có thể định vị lại khác và các tệp đối tượng được chia sẻ, dẫn đến một tệp đối tượng mới được tạo. Trong trường hợp thứ hai, khi chương trình được khởi chạy để thực thi, hệ điều hành có thể liên kết động nó với tệp thực thi của chương trình, do đó hình ảnh thực thi của chương trình sẽ được tạo. Trong trường hợp thứ hai, chúng ta đang nói về các thư viện được chia sẻ.

3. Thực thi được lưu trữ một mô tả đầy đủ cho phép hệ thống tạo ra hình ảnh của quá trình. Nó chứa các hướng dẫn, dữ liệu, mô tả về các tệp đối tượng được chia sẻ bắt buộc, và các thông tin gỡ lỗi và biểu tượng cần thiết.

Trên hình. 2.4 cho thấy cấu trúc của tệp thực thi, trong đó hệ điều hành có thể tạo một hình ảnh chương trình và chạy chương trình để thực thi.

Cơm. 2,4. Cấu trúc của tệp thực thi ở định dạng ELF

Tiêu đề có một vị trí cố định trong tệp. Phần còn lại của các thành phần được đặt theo thông tin được lưu trữ trong tiêu đề. Do đó, tiêu đề chứa mô tả chung về cấu trúc tệp, vị trí của các thành phần riêng lẻ và kích thước của chúng.

Vì tiêu đề của tệp ELF xác định cấu trúc của nó, chúng ta hãy xem xét nó chi tiết hơn (Bảng 2.4).

Bảng 2.3. Trường tiêu đề ELF

Đồng ruộng Sự miêu tả
e_ident Một mảng các byte, mỗi byte xác định một số đặc điểm chung của tệp: định dạng tệp (ELF), số phiên bản, kiến ​​trúc hệ thống (32-bit hoặc 64-bit), v.v.
loại E Loại tệp ở định dạng ELF hỗ trợ nhiều loại
e_machine Kiến trúc của nền tảng phần cứng mà tệp này đã được tạo. Trong bảng. 2.4 hiển thị các giá trị có thể có của trường này
e_version Số phiên bản của định dạng ELF. Thường được định nghĩa là EV_CURRENC (hiện tại), có nghĩa là phiên bản mới nhất
e_entry Địa chỉ ảo mà hệ thống sẽ chuyển quyền kiểm soát sau khi tải chương trình (điểm vào)
e_phoff Vị trí (độ lệch từ đầu tệp) của bảng tiêu đề chương trình
e_shoff Vị trí bảng tiêu đề phần
e_ehsize Kích thước tiêu đề
e_phentsize Kích thước của mỗi tiêu đề chương trình
e_phnum Số lượng tiêu đề chương trình
e_shentsize Kích thước của mỗi tiêu đề phân đoạn (phần)
e_shnum Số lượng tiêu đề phân đoạn (phần)
e_shstrndx Vị trí của phân đoạn chứa bảng chuỗi

Bảng 2.4. Giá trị của trường e_machine của tiêu đề tệp ELF

Nghĩa Nền tảng phần cứng
EM_M32 AT&T WE 32100
EM_SPARC CN SPARC
EM_386 Intel 80386
EM_68K Motorola 68000
EM_88K Motorola 88000
EM_486 Intel 80486
EM_860 Intel i860
EM_MIPS MIPS RS3000 Big Endian
EM_MIPS_RS3_LE MIPS RS3000 Little Endian
EM_RS6000 RS6000
EM_PA_RISC PA-RISC
EM_nCUBE nCUBE
EM_VPP500 Fujitsu VPP500
EM_SPARC32PLUS Sun SPARC 32+

Thông tin có trong bảng tiêu đề chương trình cho hạt nhân biết cách tạo một hình ảnh quá trình từ các phân đoạn. Hầu hết các phân đoạn được sao chép (ánh xạ) vào bộ nhớ và đại diện cho các phân đoạn tương ứng của một quá trình khi nó được thực thi, chẳng hạn như mã hoặc phân đoạn dữ liệu.

Mỗi tiêu đề phân đoạn chương trình mô tả một phân đoạn và chứa thông tin sau:

Loại phân đoạn và các hành động của hệ điều hành với phân đoạn này

Vị trí của phân đoạn trong tệp

Địa chỉ bắt đầu của phân đoạn trong bộ nhớ ảo của quá trình

Kích thước phân đoạn tệp

Kích thước đoạn bộ nhớ

Cờ truy cập phân đoạn (ghi, đọc, thực thi)

Một số phân đoạn có kiểu LOAD, hướng dẫn hạt nhân tạo cấu trúc dữ liệu tương ứng với các phân đoạn này, được gọi là khu vực, xác định các phần liền kề của bộ nhớ ảo của quá trình và các thuộc tính liên quan của chúng. Phân đoạn có vị trí trong tệp ELF được chỉ ra trong tiêu đề chương trình tương ứng, sẽ được ánh xạ tới vùng đã tạo, địa chỉ bắt đầu ảo của vùng này cũng được chỉ ra trong tiêu đề chương trình. Các phân đoạn thuộc loại này bao gồm, ví dụ, các phân đoạn chứa các chỉ dẫn chương trình (mã) và dữ liệu của nó. Nếu kích thước phân đoạn nhỏ hơn kích thước vùng, không gian chưa sử dụng có thể bị lấp đầy bởi các số không. Cơ chế như vậy được sử dụng đặc biệt khi tạo dữ liệu quy trình chưa được khởi tạo (BSS). Chúng ta sẽ nói thêm về các lĩnh vực trong Chương 3.

Một phân đoạn kiểu INTERP lưu trữ một trình thông dịch chương trình. Loại phân đoạn này được sử dụng cho các chương trình yêu cầu liên kết động. Bản chất của liên kết động là các thành phần riêng lẻ của tệp thực thi (tệp đối tượng dùng chung) được kết nối không phải ở giai đoạn biên dịch, mà ở giai đoạn khởi chạy chương trình để thực thi. Tên của tệp đó là trình chỉnh sửa liên kết động, được lưu trữ trong phân đoạn này. Trong quá trình thực thi một chương trình, hạt nhân tạo ra một hình ảnh quá trình bằng cách sử dụng trình liên kết được chỉ định. Do đó, nó không phải là chương trình gốc ban đầu được tải vào bộ nhớ, mà là trình liên kết động. Trong bước tiếp theo, trình liên kết động làm việc với hạt nhân UNIX để tạo ra một hình ảnh thực thi hoàn chỉnh. Trình chỉnh sửa động tải các tệp đối tượng được chia sẻ cần thiết, có tên được lưu trữ trong các phân đoạn riêng biệt của tệp thực thi nguồn và thực hiện vị trí và liên kết cần thiết. Cuối cùng, quyền điều khiển được chuyển sang chương trình ban đầu.

Cuối cùng, bảng tiêu đề hoàn thành tệp. phần hoặc phần(tiết diện). Phần (phần) xác định các phần của tệp được sử dụng để liên kết với các mô-đun khác trong quá trình biên dịch hoặc liên kết động. Theo đó, các tiêu đề chứa tất cả các thông tin cần thiết để mô tả các phần này. Theo quy tắc, các phần chứa thông tin chi tiết hơn về các phân đoạn. Vì vậy, ví dụ: một đoạn mã có thể bao gồm một số phần, chẳng hạn như bảng băm để lưu trữ các chỉ mục của các ký hiệu được sử dụng trong chương trình, một phần dành cho mã khởi tạo của chương trình, một bảng liên kết được sử dụng bởi trình soạn thảo động và phần chứa các hướng dẫn chương trình thực tế.

Chúng ta sẽ quay lại định dạng ELF trong Chương 3 khi chúng ta thảo luận về tổ chức của bộ nhớ ảo tiến trình, nhưng bây giờ chúng ta hãy chuyển sang định dạng phổ biến tiếp theo, COFF.

Từ cuốn sách Nghệ thuật lập trình Unix tác giả Raymond Eric Steven

Từ sách Hướng dẫn Máy tính tác giả Kolisnichenko Denis Nikolaevich

Từ cuốn sách Tóm tắt, thuật ngữ, văn bằng trên máy tính tác giả Balovsyak Nadezhda Vasilievna

5.2.6. Định dạng INI của Windows Nhiều chương trình trong Microsoft Windows sử dụng định dạng dữ liệu dựa trên văn bản, chẳng hạn như ví dụ trong Ví dụ 5-6. Trong ví dụ này, các tài nguyên tùy chọn có tên tài khoản, thư mục, numeric_id và nhà phát triển được liên kết với các dự án có tên là python, sng, f etchmail và py-howto. Đang ghi âm

Từ sách Hướng dẫn sử dụng máy tính mới nhất tác giả Beluntsov Valery

14.5.3. Định dạng ô Định dạng chỉ định cách giá trị của ô sẽ được hiển thị. Định dạng có liên quan chặt chẽ đến kiểu dữ liệu của ô. Loại là tùy thuộc vào bạn. Nếu bạn đã nhập một số, thì đó là kiểu dữ liệu số. Bản thân Excel cố gắng xác định định dạng theo kiểu dữ liệu. Ví dụ: nếu bạn đã nhập văn bản, thì

Từ cuốn sách Nghệ thuật lập trình Unix tác giả Raymond Eric Steven

Định dạng PDF PDF là viết tắt của Định dạng tài liệu di động (Portable Document Format). Định dạng này được tạo ra đặc biệt để loại bỏ các vấn đề với việc hiển thị thông tin trong tệp. Ưu điểm của nó là, thứ nhất, một tài liệu được lưu ở định dạng PDF sẽ giống nhau

Từ cuốn sách Kiến trúc TCP / IP, Giao thức, Triển khai (bao gồm IP phiên bản 6 và Bảo mật IP) tác giả Faith Sidney M

Định dạng tệp Khi người dùng bắt đầu làm việc với một tệp, hệ thống cần biết nó được viết ở định dạng nào và nó sẽ được mở bằng chương trình nào. Ví dụ: nếu một tệp chứa văn bản thuần túy, thì nó có thể được đọc trong bất kỳ chương trình văn bản nào

Từ cuốn sách Yandex cho mọi người tác giả Abramzon M. G.

5.2.2. Định dạng RFC 822 Siêu định dạng RFC 822 có nguồn gốc từ định dạng văn bản của thư điện tử Internet. RFC 822 là tiêu chuẩn RFC Internet chính mô tả định dạng này (sau đó được thay thế bởi RFC 2822). Định dạng MIME (Tiện ích mở rộng đa phương tiện Internet)

Từ Macromedia Flash Professional 8. Đồ họa và Hoạt hình tác giả Dronov V. A.

5.2.3. Định dạng cookie-Jar Định dạng cookie-jar được sử dụng bởi lộc (1) cho cơ sở dữ liệu của riêng nó về các trích dẫn ngẫu nhiên. Nó phù hợp cho các mục chỉ đơn giản là các khối văn bản không có cấu trúc. Dấu phân tách bản ghi ở định dạng này là ký tự

Từ sách Xử lý âm thanh máy tính tác giả Zagumennov Alexander Petrovich

5.2.4. Định dạng record-jar Các dấu phân cách bản ghi cookie-jar phù hợp với siêu định dạng RFC 822 cho các bản ghi tạo nên định dạng được đề cập trong cuốn sách này là "record-jar". Đôi khi cần có định dạng văn bản hỗ trợ nhiều mục nhập với một bộ tên rõ ràng khác nhau

Từ sách Hệ điều hành UNIX tác giả Robachevsky Andrey M.

5.2.6. Định dạng INI của Windows Nhiều chương trình trong Microsoft Windows sử dụng định dạng dữ liệu dựa trên văn bản, chẳng hạn như ví dụ trong Ví dụ 5-6. Trong ví dụ này, các tài nguyên tùy chọn có tên tài khoản, thư mục, numeric_id và nhà phát triển được liên kết với các dự án python, sng, fetchmail và py-howto được đặt tên. Đang ghi âm

Từ sách Máy tính văn phòng cho phụ nữ tác giả Pasternak Evgenia

19.5 Định dạng URL chung Tóm tắt những điều trên, chúng tôi lưu ý rằng:? URL bắt đầu với giao thức truy cập được sử dụng.? Đối với tất cả các ứng dụng ngoại trừ tin tức trực tuyến và email, theo sau là dấu phân cách: //.? Sau đó, tên máy chủ của máy chủ được chỉ định.? Cuối cùng

Từ sách của tác giả

3.3.1. Định dạng RSS Bạn có thể đọc tin tức trang web theo nhiều cách khác nhau. Cách dễ nhất là thỉnh thoảng ghé thăm trang web và xem các tin nhắn mới. Theo

Từ sách của tác giả

Định dạng MP3 Định dạng MP3 được tạo ra để phân phối các tệp nhạc được nén bằng codec MPEG 1 cấp 3. Đây hiện là định dạng phổ biến nhất để phân phối nhạc qua Internet và hơn thế nữa. Nó được hỗ trợ hoàn toàn bởi tất cả các chương trình ghi và xử lý âm thanh, cho

Từ sách của tác giả

Định dạng MP3 Phương pháp nén âm thanh, cũng như định dạng tệp âm thanh nén, do tổ chức quốc tế MPEG (Moving Pictures Experts Group - Nhóm chuyên gia ghi âm video) đề xuất, dựa trên mã hóa âm thanh cảm nhận. Làm việc trên việc tạo ra các thuật toán mã hóa hiệu quả

Từ sách của tác giả

Định dạng ELF Định dạng ELF có một số loại tệp mà chúng ta đã gọi khác nhau cho đến nay, chẳng hạn như tệp thực thi hoặc tệp đối tượng. Tuy nhiên, tiêu chuẩn ELF phân biệt giữa các loại sau: 1. Một tệp có thể định vị lại chứa các hướng dẫn và dữ liệu có thể

Từ sách của tác giả

Định dạng số Cuối cùng đã đến định dạng số. Tôi đã đề cập đến nó nhiều hơn một lần, bây giờ tôi sẽ đặt mọi thứ lên giá (mặc dù bạn có thể đã hiểu ý nghĩa chung) Các số trong Excel có thể được hiển thị ở nhiều định dạng khác nhau. Trong phần này, chúng ta sẽ nói về những định dạng số nào tồn tại và cách

Trong bài đánh giá này, chúng tôi sẽ chỉ nói về phiên bản 32-bit của định dạng này, vì chúng tôi chưa cần phiên bản 64-bit.

Bất kỳ tệp ELF nào (bao gồm các mô-đun đối tượng của định dạng này) bao gồm các phần sau:

  • Tiêu đề tệp ELF;
  • Bảng các phần chương trình (có thể vắng mặt trong các môđun đối tượng);
  • Các phần của tệp ELF;
  • Bảng phần (có thể không có trong mô-đun thực thi);
  • Vì lý do hiệu suất, định dạng ELF không sử dụng trường bit. Và tất cả các cấu trúc thường được căn chỉnh 4 byte.

Bây giờ chúng ta hãy xem xét các kiểu được sử dụng trong tiêu đề của tệp ELF:

Bây giờ hãy xem xét tiêu đề tệp:

#define EI_NIDENT 16 struct elf32_hdr ( unsigned char e_ident; Elf32_Half e_type; Elf32_Half e_machine; Elf32_Word e_version; Elf32_Addr e_entry; /* Entry point */ Elf32_Off e_phoff; Elf32_Off e_shoff; Elf32_Word e_flags; Elf32_Half e_ehsize; Elf32_Half e_phentsize; Elf32_Half e_phnum; Elf32_Half e_shentsize; Elf32_Half e_shnum; Elf32_Half e_shstrndx;);

Mảng e_ident chứa thông tin về hệ thống và bao gồm một số trường con.

Struct (unsigned char ei_magic; unsigned char ei_class; unsigned char ei_data; unsigned char ei_version; unsigned char ei_pad;)

  • ei_magic - giá trị không đổi cho tất cả các tệp ELF, bằng (0x7f, "E", "L", "F")
  • ei_class - lớp tệp ELF (1 - 32 bit, 2 - 64 bit mà chúng tôi không xem xét)
  • ei_data - xác định thứ tự byte cho tệp này (thứ tự này phụ thuộc vào nền tảng và có thể trực tiếp (LSB hoặc 1) hoặc ngược lại (MSB hoặc 2)) Đối với bộ xử lý Intel, chỉ giá trị 1 được phép.
  • ei_version là một trường khá vô dụng và nếu không bằng 1 (EV_CURRENT) thì tệp được coi là không hợp lệ.

Trường ei_pad là nơi hệ điều hành lưu trữ thông tin nhận dạng của chúng. Trường này có thể trống. Nó cũng không quan trọng đối với chúng tôi.

Trường tiêu đề e_type có thể chứa nhiều giá trị, đối với các tệp thực thi, nó phải ET_EXEC bằng 2

e_machine - xác định bộ xử lý mà tệp thực thi này có thể chạy (Đối với chúng tôi, giá trị của EM_386 là 3)

Trường e_version tương ứng với trường ei_version từ tiêu đề.

Trường e_entry xác định địa chỉ bắt đầu của chương trình, địa chỉ này được đặt trong eip trước khi bắt đầu chương trình.

Trường e_phoff chỉ định độ lệch từ đầu tệp nơi chứa bảng phần chương trình, được sử dụng để tải chương trình vào bộ nhớ.

Tôi sẽ không liệt kê mục đích của tất cả các trường, không phải tất cả đều cần thiết để tải. Tôi sẽ chỉ mô tả hai điều nữa.

Trường e_phentsize xác định kích thước của mục nhập trong bảng phần chương trình.

Và trường e_phnum chỉ định số lượng mục trong bảng phần chương trình.

Bảng phần (không phải chương trình) được sử dụng để liên kết các chương trình. chúng tôi sẽ không xem xét nó. Ngoài ra, chúng tôi sẽ không xem xét các mô-đun được liên kết động. Chủ đề này khá phức tạp, không thích hợp cho người mới làm quen lần đầu. :)

Bây giờ về các phần của chương trình. Định dạng của mục nhập bảng phần chương trình như sau:

Struct elf32_phdr (Elf32_Word p_type; Elf32_Off p_offset; Elf32_Addr p_vaddr; Elf32_Addr p_paddr; Elf32_Word p_filesz; Elf32_Word p_memsz; Elf32_Word p_flags; Elf32_Word p_flags;

Thông tin thêm về các lĩnh vực.

  • p_type - xác định kiểu của phần chương trình. Nó có thể nhận một số giá trị, nhưng chúng tôi chỉ quan tâm đến một. PT_LOAD (1). Nếu phần thuộc loại này, thì nó sẽ được tải vào bộ nhớ.
  • p_offset - xác định độ lệch trong tệp mà từ đó phần này bắt đầu.
  • p_vaddr Chỉ định địa chỉ ảo nơi phần này sẽ được tải vào bộ nhớ.
  • p_paddr - xác định địa chỉ vật lý nơi phần này sẽ được tải. Trường này không cần phải sử dụng và chỉ có ý nghĩa đối với một số nền tảng.
  • p_filesz - Xác định kích thước của một phần trong tệp.
  • p_memsz - xác định kích thước của một phần trong bộ nhớ. Giá trị này có thể lớn hơn giá trị trước đó. Trường p_flag xác định kiểu truy cập vào các phần trong bộ nhớ. Một số phần được phép biểu diễn, một số phần được ghi lại. Mọi người đều có sẵn để đọc trong các hệ thống hiện có.

Đang tải định dạng ELF.

Với tiêu đề, chúng tôi đã hình dung ra một chút. Bây giờ tôi sẽ đưa ra một thuật toán để tải một tệp nhị phân có định dạng ELF. Thuật toán là giản đồ, bạn không nên coi nó như một chương trình làm việc.

Int LoadELF (unsigned char * bin) (struct elf32_hdr * EH = (struct elf32_hdr *) bin; struct elf32_phdr * EPH; if (EH-> e_ident! = 0x7f || // Điều khiển MAGIC EH-> e_ident! = "E" || EH-> e_ident! = "L" || EH-> e_ident! = "F" || EH-> e_ident! = ELFCLASS32 || // Lớp điều khiển EH-> e_ident! = ELFDATA2LSB || // thứ tự byte EH-> e_ident! = EV_CURRENT || // phiên bản EH-> e_type! = ET_EXEC || // nhập EH-> e_machine! = EM_386 || // nền tảng EH-> e_version! = EV_CURRENT) // và một phiên bản nữa, chỉ trong trường hợp trả về ELF_WRONG; EPH = (struct elf32_phdr *) (bin + EH-> e_phoff); while (EH-> e_phnum--) (if (EPH-> p_type == PT_LOAD) memcpy (EPH-> p_vaddr, bin + EPH-> p_offset, EPH-> p_filesz); EPH = (struct elf32_phdr *) ((unsigned char *) EPH + EH-> e_phentsize));) return ELF_OK; )

Một lưu ý nghiêm túc là bạn nên phân tích các trường EPH-> p_flags và đặt quyền truy cập vào các trang tương ứng và chỉ cần sao chép sẽ không hoạt động ở đây, nhưng điều này không còn áp dụng cho định dạng nữa mà cho phân bổ bộ nhớ. Vì vậy, chúng tôi sẽ không nói về nó bây giờ.

Định dạng PE.

Theo nhiều cách, nó tương tự như định dạng ELF, và không có gì ngạc nhiên khi cũng nên có sẵn các phần để tải xuống.

Giống như mọi thứ trong Microsoft :) định dạng PE dựa trên định dạng EXE. Cấu trúc tệp là:

  • 00h - Tiêu đề EXE (Tôi sẽ không xem xét nó, nó cũ như Dos. :)
  • 20h - tiêu đề OEM (không có gì đáng kể trong đó);
  • 3ch - độ lệch tiêu đề PE thực trong tệp (dword).
  • bàn di chuyển sơ khai;
  • sơ khai;
  • Tiêu đề PE;
  • bảng đối tượng;
  • các đối tượng tệp;

sơ khai là một chương trình chạy ở chế độ thực và thực hiện một số công việc sơ bộ. Nó có thể không có sẵn, nhưng đôi khi nó có thể cần thiết.

Chúng tôi quan tâm đến một thứ khác, tiêu đề PE.

Cấu trúc của nó như thế này:

Struct pe_hdr (pe_sign dài không dấu; pe_cputype ngắn không dấu; pe_objnum ngắn không dấu; pe_time ngắn không dấu; pe_time dài không dấu; pe_cofftbl_off dài không dấu; pe_cofftbl_size không dấu; pe_nthdr_size; không dấu ; unsigned long pe_entry; unsigned long pe_code_base; unsigned long pe_data_base; unsigned long pe_image_base; unsigned long pe_obj_align; unsigned long pe_file_align; // ... à, và rất nhiều thứ khác, không quan trọng.);

Có rất nhiều thứ ở đó. Chỉ cần nói rằng kích thước của tiêu đề này là 248 byte.

Và điều chính là hầu hết các trường này không được sử dụng. (Ai xây dựng như vậy?) Không, tất nhiên, họ có mục đích rõ ràng, nhưng chương trình thử nghiệm của tôi, chẳng hạn, chứa các số không trong các trường pe_code_base, pe_code_size, v.v., nhưng nó hoạt động tốt. Kết luận gợi ý rằng tệp được tải dựa trên bảng các đối tượng. Đó là những gì chúng ta sẽ nói về.

Bảng đối tượng đứng ngay sau tiêu đề PE. Các mục nhập trong bảng này có định dạng sau:

Struct pe_ohdr (unsigned char o_name; unsigned long o_vsize; unsigned long o_vaddr; unsigned long o_psize; unsigned long o_poff; unsigned char o_reserved; unsigned long o_flags;);

  • o_name - tên phần, nó hoàn toàn không quan tâm để tải;
  • o_vsize - kích thước phần trong bộ nhớ;
  • o_vaddr - địa chỉ bộ nhớ liên quan đến ImageBase;
  • o_psize - kích thước phần trong tệp;
  • o_poff - phần bù đắp trong tệp;
  • o_flags - cờ phần;

Đây là giá trị xem xét các lá cờ một cách chi tiết hơn.

  • 00000004h - được sử dụng cho mã có hiệu số 16 bit
  • 00000020h - phần mã
  • 00000040h - phần dữ liệu khởi tạo
  • 00000080h - phần dữ liệu chưa khởi tạo
  • 00000200h - nhận xét hoặc bất kỳ loại thông tin nào khác
  • 00000400h - phần lớp phủ
  • 00000800h - sẽ không phải là một phần của hình ảnh chương trình
  • 00001000h - dữ liệu chung
  • 00500000h - căn chỉnh mặc định trừ khi được chỉ định khác
  • 02000000h - có thể được dỡ bỏ khỏi bộ nhớ
  • 04000000h - không được lưu trong bộ nhớ đệm
  • 08000000h - không thể phân trang
  • 10000000h - đã chia sẻ
  • 20000000h - khả thi
  • 40000000h - có thể đọc được
  • 80000000h - bạn có thể viết

Một lần nữa, tôi sẽ không ở phần chia sẻ và lớp phủ, chúng tôi quan tâm đến mã, dữ liệu và quyền truy cập.

Nói chung, thông tin này đã đủ để tải xuống tệp nhị phân.

Đang tải định dạng PE.

int LoadPE (unsigned char * bin) (struct elf32_hdr * PH = (struct pe_hdr *) (bin + * ((unsigned long *) & bin)); // Tất nhiên, sự kết hợp không rõ ràng ... chỉ cần lấy mật khẩu tại offset 0x3c / / Và tính địa chỉ tiêu đề PE trong ảnh tệp struct elf32_phdr * POH; if (PH == NULL || // Điều khiển con trỏ PH-> pe_sign! = 0x4550 || // Chữ ký PE ("P" , "E", 0, 0) PH-> pe_cputype! = 0x14c || // i386 (PH-> pe_flags & 2) == 0) // không thể chạy tệp! Return PE_WRONG; POH = (struct pe_ohdr *) ((unsigned char *) PH + 0xf8); while (PH-> pe_obj_num--) (if ((POH-> p_flags & 0x60)! = 0) // mã hoặc bản ghi nhớ dữ liệu đã khởi tạo (PE-> pe_image_base + POH -> o_vaddr, bin + POH-> o_poff, POH-> o_psize); POH = (struct pe_ohdr *) ((unsigned char *) POH + sizeof (struct pe_ohdr));) return PE_OK;)

Đây một lần nữa không phải là một chương trình đã hoàn thành, mà là một thuật toán tải.

Và một lần nữa, nhiều điểm không được đề cập, vì chúng vượt ra ngoài chủ đề.

Nhưng bây giờ cần nói một chút về các tính năng hiện có của hệ thống.

Các tính năng của hệ thống.

Mặc dù tính linh hoạt của các biện pháp bảo vệ có sẵn trong bộ xử lý (bảo vệ ở cấp bảng mô tả, bảo vệ ở cấp phân đoạn, bảo vệ cấp trang), trong các hệ thống hiện có (cả trong Windows và Unix), chỉ có bảo vệ trang được sử dụng đầy đủ, mặc dù nó có thể giữ cho mã không được viết, nhưng không thể giữ cho dữ liệu không được thực thi. (Có thể đây là lý do cho sự phong phú của các lỗ hổng hệ thống?)

Tất cả các phân đoạn được đánh địa chỉ từ 0 địa chỉ tuyến tính và mở rộng đến cuối bộ nhớ tuyến tính. Quá trình phân giới chỉ xảy ra ở cấp bảng trang.

Về vấn đề này, tất cả các mô-đun được liên kết không phải từ các địa chỉ bắt đầu, nhưng với một khoảng chênh lệch đủ lớn trong phân đoạn. Trên Windows, địa chỉ cơ sở trong phân đoạn là 0x400000, trên Unix (Linux hoặc FreeBSD) là 0x8048000.

Một số tính năng cũng liên quan đến phân trang bộ nhớ.

Các tệp ELF được liên kết theo cách mà ranh giới và kích thước của các phần nằm trên 4 kilobyte khối của tệp.

Và ở định dạng PE, mặc dù thực tế là bản thân định dạng cho phép bạn căn chỉnh các phần 512 byte, căn chỉnh phần 4k được sử dụng, căn chỉnh nhỏ hơn trong Windows không được coi là chính xác.

Nếu máy tính của bạn có chương trình chống vi rút có thể quét tất cả các tệp trên máy tính, cũng như từng tệp riêng lẻ. Bạn có thể quét bất kỳ tệp nào bằng cách nhấp chuột phải vào tệp và chọn tùy chọn thích hợp để quét vi-rút cho tệp.

Ví dụ, trong hình này, tập tin my-file.elf, sau đó bạn cần nhấp chuột phải vào tệp này và trong menu tệp, hãy chọn tùy chọn "quét bằng AVG". Chọn tùy chọn này sẽ mở AVG Antivirus và quét vi-rút cho tệp.


Đôi khi một lỗi có thể do cài đặt phần mềm không chính xác, có thể do sự cố xảy ra trong quá trình cài đặt. Nó có thể can thiệp vào hệ điều hành của bạn liên kết tệp ELF của bạn với ứng dụng phần mềm phù hợp, ảnh hưởng đến cái gọi là "liên kết phần mở rộng tệp".

Đôi khi đơn giản cài đặt lại Dolphin (giả lập) có thể giải quyết vấn đề của bạn bằng cách liên kết đúng cách ELF với Dolphin (trình giả lập). Trong các trường hợp khác, sự cố liên kết tệp có thể do lập trình phần mềm tồi nhà phát triển và bạn có thể cần liên hệ với nhà phát triển để được hỗ trợ thêm.


Khuyên bảo: Hãy thử cập nhật Dolphin (giả lập) lên phiên bản mới nhất để đảm bảo bạn có các bản vá và cập nhật mới nhất.


Điều này có vẻ quá rõ ràng, nhưng thường chính tệp ELF có thể gây ra sự cố. Nếu bạn nhận được tệp qua tệp đính kèm email hoặc tải xuống từ một trang web và quá trình tải xuống bị gián đoạn (ví dụ: do mất điện hoặc lý do khác), tệp có thể bị hỏng. Nếu có thể, hãy thử lấy một bản sao mới của tệp ELF và thử mở lại.


Cẩn thận: Tệp bị hỏng có thể gây ra thiệt hại phụ cho phần mềm độc hại trước đó hoặc hiện có trên PC của bạn, vì vậy điều quan trọng là phải cập nhật máy tính của bạn với phần mềm chống vi-rút cập nhật.


Nếu tệp ELF của bạn liên kết với phần cứng trên máy tính của bạnđể mở tệp bạn có thể cần cập nhật trình điều khiển thiết bị liên kết với thiết bị này.

Vấn đề này thường được liên kết với các loại tệp phương tiện, phụ thuộc vào việc mở thành công phần cứng bên trong máy tính, ví dụ: card âm thanh hoặc card màn hình. Ví dụ: nếu bạn đang cố gắng mở một tệp âm thanh nhưng không thể mở được, bạn có thể cần phải cập nhật trình điều khiển card âm thanh.


Khuyên bảo: Nếu khi bạn cố gắng mở một tệp ELF, bạn nhận được Thông báo lỗi liên quan đến tệp .SYS, vấn đề có thể là liên quan đến trình điều khiển thiết bị bị hỏng hoặc lỗi thời cần được cập nhật. Quá trình này có thể được tạo thuận lợi bằng cách sử dụng phần mềm cập nhật trình điều khiển như DriverDoc.


Nếu các bước không giải quyết được vấn đề và bạn vẫn gặp sự cố khi mở tệp ELF, điều này có thể do thiếu tài nguyên hệ thống có sẵn. Một số phiên bản của tệp ELF có thể yêu cầu một lượng tài nguyên đáng kể (ví dụ: bộ nhớ / RAM, sức mạnh xử lý) để mở đúng cách trên máy tính của bạn. Sự cố này khá phổ biến nếu bạn đang sử dụng cùng lúc phần cứng máy tính khá cũ và hệ điều hành mới hơn nhiều.

Sự cố này có thể xảy ra khi máy tính gặp khó khăn trong việc hoàn thành tác vụ vì hệ điều hành (và các dịch vụ khác chạy nền) có thể tiêu tốn quá nhiều tài nguyên để mở tệp ELF. Thử đóng tất cả các ứng dụng trên PC của bạn trước khi mở Tệp trò chơi Nintendo Wii. Bằng cách giải phóng tất cả các tài nguyên hiện có trên máy tính của bạn, bạn sẽ đảm bảo điều kiện tốt nhất để cố gắng mở tệp ELF.


nếu bạn đã hoàn thành tất cả các bước trên và tệp ELF của bạn vẫn không mở, bạn có thể cần chạy nâng cấp phần cứng. Trong hầu hết các trường hợp, ngay cả với các phiên bản phần cứng cũ hơn, sức mạnh xử lý vẫn có thể là quá đủ cho hầu hết các ứng dụng của người dùng (trừ khi bạn đang thực hiện nhiều công việc đòi hỏi nhiều CPU như kết xuất 3D, lập mô hình tài chính / khoa học hoặc công việc chuyên sâu về truyền thông ). Vì vậy, có khả năng là máy tính của bạn không có đủ bộ nhớ(thường được gọi là "RAM", hoặc RAM) để thực hiện tác vụ mở tệp.

Phiên bản của câu trả lời này với TOC tốt và nhiều nội dung hơn: http://www.cirosantilli.com/elf-hello-world (nhấp vào đây giới hạn 30 nghìn ký tự)

Tiêu chuẩn

ELF do LSB cung cấp:

  • chung cốt lõi: http://refspecs.linuxfoundation.org/LSB_4.1.0/LSB-Core-generic/LSB-Core-generic/elf-generic.html
  • lõi AMD64: http://refspecs.linuxfoundation.org/LSB_4.1.0/LSB-Core-AMD64/LSB-Core-AMD64/book1.html

LSB chủ yếu đề cập đến các tiêu chuẩn khác với các phần mở rộng nhỏ, cụ thể là:

    chung chung (cả hai của SCO):

    • Hệ thống V ABI 4.1 (1997) http://www.sco.com/developers/devspecs/gabi41.pdf, không phải 64 bit, mặc dù một số ma thuật được dành riêng cho nó. Đối với các tệp chính cũng vậy.
    • Hệ thống V ABI Cập nhật DRAFT 17 (2003) http://www.sco.com/developers/gabi/2003-12-17/contents.html thêm 64 bit. Chỉ cập nhật chương 4 và 5 của tài liệu trước: phần còn lại vẫn có giá trị và vẫn được tham khảo.
  • kiến trúc cụ thể:

    • IA-32: http://refspecs.linuxfoundation.org/LSB_4.1.0/LSB-Core-IA32/LSB-Core-IA32/elf-ia32.html trỏ chủ yếu đến http://www.sco.com/developers/ devspecs / abi386-4.pdf
    • AMD64: http://refspecs.linuxfoundation.org/LSB_4.1.0/LSB-Core-AMD64/LSB-Core-AMD64/elf-amd64.html, về cơ bản trỏ đến tài liệu http://www.x86-64.org/ /abi.pdf

Một sơ yếu lý lịch hữu ích có thể được tìm thấy tại:

Cấu trúc của nó có thể được kiểm tra bằng cách sử dụng các cách thân thiện với người dùng như readelf và objdump.

Tạo một ví dụ

Hãy chia nhỏ một ví dụ thực thi Linux x86-64 tối thiểu:

Phần .data hello_world db "Hello world!", 10 hello_world_len equ $ - phần hello_world .text global _start _start: mov rax, 1 mov rdi, 1 mov rsi, hello_world mov rdx, hello_world_len syscall mov rax, 60 mov rdi, 0 syscall

Tổng hợp với

Nasm -w + all -f elf64 -o "hello_world.o" "hello_world.asm" ld -o "hello_world.out" "hello_world.o"

  • NASM 2.10.09
  • Binutils phiên bản 2.24 (chứa ld)
  • Ubuntu 14.04

Chúng tôi không sử dụng chương trình C vì điều đó sẽ làm phức tạp phân tích sẽ là cấp 2 :-)

biểu diễn hệ thập lục phân của nhị phân

hd hello_world.o hd hello_world.out

Cấu trúc tệp chung

Tệp ELF chứa các phần sau:

  • Tiêu đề ELF. Cho biết vị trí của bảng tiêu đề phần và bảng tiêu đề chương trình.

    Bảng tiêu đề phần (tùy chọn trong tệp thực thi). Mỗi người trong số họ có tiêu đề phần e_shnum, mỗi tiêu đề cho biết vị trí của phần.

    N phân vùng với N<= e_shnum (необязательно в исполняемом файле)

    Bảng tiêu đề chương trình (chỉ dành cho các tệp thực thi). Mỗi cái này có tiêu đề chương trình e_phnum, mỗi tiêu đề cho biết vị trí của phân đoạn.

    N phân đoạn, với N<= e_phnum (необязательно в исполняемом файле)

Thứ tự của các phần này không cố định: điều cố định duy nhất là tiêu đề ELF, tiêu đề này phải nằm đầu tiên trong tệp:

Tiêu đề ELF

Cách dễ nhất để xem tiêu đề là:

tự đọc -h hello_world.o tự đọc -h hello_world.out

Byte trong tệp đối tượng:

00000000 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 |. TỰ ........... | 00000010 01 00 3e 00 01 00 00 00 00 00 00 00 00 00 00 | ..> ............. | 00000020 00 00 00 00 00 00 00 00 40 00 00 00 00 00 00 00 | [email được bảo vệ]| 00000030 00 00 00 00 40 00 00 00 00 00 40 00 07 00 03 00 |[email được bảo vệ]@.....|

00000000 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 |. TỰ ........... | 00000010 02 00 3e 00 01 00 00 00 b0 00 40 00 00 00 00 00 | ..> [email được bảo vệ]| 00000020 40 00 00 00 00 00 00 00 10 01 00 00 00 00 00 00 |@...............| 00000030 00 00 00 00 40 00 38 00 02 00 40 00 06 00 03 00 |[email được bảo vệ]@.....|

Cấu trúc trình bày:

Typedef struct ( unsigned char e_ident; Elf64_Half e_type; Elf64_Half e_machine; Elf64_Word e_version; Elf64_Addr e_entry; Elf64_Off e_phoff; Elf64_Off e_shoff; Elf64_Word e_flags; Elf64_Half e_ehsize; Elf64_Half e_phentsize; Elf64_Half e_phnum; Elf64_Half e_shentsize; Elf64_Half e_shnum; Elf64_Half e_shstrndx; ) Elf64_Ehdr;

Phân rã bằng tay:

    0 0: EI_MAG = 7f 45 4c 46 = 0x7f "E", "L", "F": số phép thuật ELF

    0 4: EI_CLASS = 02 = ELFCLASS64: elf 64-bit

    0 5: EI_DATA = 01 = ELFDATA2LSB: dữ liệu lớn

    0 6: EI_VERSION = 01: phiên bản định dạng

    0 7: EI_OSABI (chỉ dành cho năm 2003) = 00 = ELFOSABI_NONE: Không có phần mở rộng.

    0 8: EI_PAD = 8x 00: byte dành riêng. Phải được đặt thành 0.

    1 0: e_type = 01 00 = 1 (big endian) = ET_REl: định dạng có thể di dời

    Trong 02 00 thực thi cho ET_EXEC.

    1 2: e_machine = 3e 00 = 62 = EM_X86_64: Kiến trúc AMD64

    1 4: e_version = 01 00 00 00: nên là 1

    1 8: e_entry = 8x 00: điểm nhập của địa chỉ thực thi, hoặc 0 nếu không áp dụng được đối với tệp đối tượng, vì không có điểm nhập.

    Trong tệp thực thi, đây là b0 00 40 00 00 00 00 00. VIỆC CẦN LÀM: chúng ta có thể cài đặt những gì khác? Kernel dường như đặt IP trực tiếp vào giá trị này, nó không được mã hóa cứng.

    2 0: e_phoff = 8x 00: độ lệch bảng tiêu đề chương trình, 0 nếu không.

    40 00 00 00 trong tệp thực thi, nghĩa là nó bắt đầu ngay sau tiêu đề ELF.

    2 8: e_shoff = 40 7x 00 = 0x40: phần bù tệp bảng tiêu đề, 0 nếu không có.

    3 0: e_flags = 00 00 00 00 VIỆC CẦN LÀM. Đặc biệt cho Arch.

    3 4: e_ehsize = 40 00: kích thước của tiêu đề elf này. Tại sao lại là lĩnh vực này? Làm thế nào điều này có thể thay đổi?

    3 6: e_phentsize = 00 00: kích thước của mỗi tiêu đề chương trình, 0 nếu không có.

    38 00 trong tệp thực thi: độ dài tệp là 56 byte

    3 8: e_phnum = 00 00: số mục tiêu đề chương trình, 0 nếu không có.

    02 00 trong tệp thực thi: có 2 mục nhập.

    3 A: e_shentsize và e_shnum = 40 00 07 00: kích thước tiêu đề phần và số lượng mục nhập

Bảng tiêu đề phần

Một mảng cấu trúc Elf64_Shdr.

Mỗi mục nhập chứa siêu dữ liệu về phần đó.

e_shoff của tiêu đề ELF đưa ra vị trí bắt đầu ở đây, 0x40.

e_shentsize và e_shnum từ tiêu đề ELF cho biết chúng ta có 7 mục nhập, mỗi mục dài 0x40.

Vì vậy, bảng có các byte từ 0x40 đến 0x40 + 7 + 0x40 - 1 = 0x1FF.

Một số tiêu đề phần được dành riêng cho một số loại phần nhất định: ví dụ: http://www.sco.com/developers/gabi/2003-12-17/ch4.sheader.html#special_silities. .text yêu cầu loại SHT_PROGBITS và SHF_ALLOC + SHF_EXECINSTR

readelf -S hello_world.o:

Có 7 tiêu đề phần, bắt đầu từ độ lệch 0x40: Tiêu đề phần: Tên Loại Địa chỉ Kích thước bù đắp Kích thước EntSize Cờ Liên kết Thông tin liên kết Căn chỉnh [0] NULL 0000000000000000 00000000 0000000000000000 0000000000000000 0 0 0 [1] .data PROGBITS 0000000000000000 000002000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002] .text PROGBITS 0000000000000000 00000210 0000000000000027 0000000000000000 AX 0 0 16 [ 3] .shstrtab STRTAB 0000000000000000 00000240 0000000000000032 0000000000000000 0 0 1 [ 4] .symtab SYMTAB 0000000000000000 00000280 00000000000000a8 0000000000000018 5 6 4 [ 5] .strtab STRTAB 0000000000000000 00000330 0000000000000034 0000000000000000 0 0 1 [6] .rela.text RELA 0000000000000000 00000370 0000000000000018 0000000000000018 4 2 4 Phím để gắn cờ: W (ghi), A (phân bổ), X (thực thi), M (hợp nhất), S (chuỗi), l (lớn) I (thông tin), L (thứ tự liên kết), G (nhóm), T (TLS), E (loại trừ), x (không xác định) O (yêu cầu xử lý thêm hệ điều hành) o (hệ điều hành cụ thể), p (bộ xử lý cụ thể)

struct, được đại diện bởi mỗi mục nhập:

Cấu trúc Typedef (Elf64_Word sh_type; Elf64_Word sh_type; Elf64_Xword sh_flags; Elf64_Addr sh_addr; Elf64_Off sh_offset; Elf64_Xword sh_size; Elf64_Word sh_link; Elf64_Word shadize_ralign; Elf64_Word shadize_izeXfo)

Phần

Mục lục 0

Được chứa trong byte 0x40 đến 0x7F.

Phần đầu tiên luôn luôn kỳ diệu: http://www.sco.com/developers/gabi/2003-12-17/ch4.sheader.html nói:

Nếu số lượng phân vùng lớn hơn hoặc bằng SHN_LORESERVE (0xff00), e_shnum được đặt thành SHN_UNDEF (0) và số lượng mục nhập bảng tiêu đề phân vùng thực tế được chứa trong trường sh_size của tiêu đề phân vùng ở chỉ mục 0 (nếu không, thành viên sh_size của mục nhập ban đầu chứa 0).

Có những phần kỳ diệu khác trong Hình 4-7: Chỉ mục Phần Đặc biệt.

Tại chỉ mục 0, SHT_NULL là bắt buộc. Có cách sử dụng nào khác cho việc này không: Mục SHT_NULL trong ELF có công dụng gì? ?

phần .data

Dữ liệu là phần 1:

00000080 01 00 00 00 01 00 00 00 03 00 00 00 00 00 00 00 | ................ | 00000090 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ | 000000a0 0d 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ | 000000b0 04 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ |

    Ở đây, 1 nói rằng tên của phần này bắt đầu từ ký tự đầu tiên của phần này và kết thúc ở ký tự NUL đầu tiên, tạo nên string.data.

    Dữ liệu là một trong những tên phần có ý nghĩa được xác định trước http://www.sco.com/developers/gabi/2003-12-17/ch4.strtab.html

    Các phần này lưu trữ dữ liệu đã khởi tạo đóng góp vào hình ảnh bộ nhớ của chương trình.

  • 80 4: sh_type = 01 00 00 00: SHT_PROGBITS: nội dung phần không được ELF chỉ định, chỉ do chương trình diễn giải nó. Không sao cả, kể từ một .data.

    80 8: sh_flags = 03 7x 00: SHF_ALLOC và SHF_EXECINSTR: http://www.sco.com/developers/gabi/2003-12-17/ch4.sheader.html#sh_flags, theo yêu cầu từ phần .data

    90 0: sh_addr = 8x 00: trong đó địa chỉ ảo mà phần sẽ được đặt trong thời gian chạy, 0 nếu không được đặt

    90 8: sh_offset = 00 02 00 00 00 00 00 00 = 0x200: số byte từ đầu chương trình đến byte đầu tiên trong phần này

    a0 0: sh_size = 0d 00 00 00 00 00 00 00

    Nếu chúng ta lấy byte 0xD, bắt đầu từ sh_offset 200, chúng ta thấy:

    00000200 48 65 6c 6c 6f 20 77 6f 72 6c 64 21 0a 00 | Hello world! .. |

    AHA! Vì vậy, chuỗi của chúng tôi "Xin chào thế giới!" nằm trong phần dữ liệu, như chúng tôi đã nói, nó nằm trên NASM.

    Khi chúng ta đã hoàn thành hd, chúng ta sẽ xem nó như thế này:

    Readelf -x .data hello_world.o

    kết quả đầu ra:

    Kết xuất hex của phần ".data": 0x00000000 48656c6c 6f20776f 726c6421 0a Hello world !.

    NASM đặt các thuộc tính phù hợp cho phần này vì nó đề cập đến .data một cách kỳ diệu: http://www.nasm.us/doc/nasmdoc7.html#section-7.9.2

    Cũng lưu ý rằng đây là phần lựa chọn sai: một trình biên dịch C tốt sẽ đặt dòng này ở dạng .rodata thay vì nó ở chế độ chỉ đọc và điều này sẽ cho phép tiếp tục tối ưu hóa hệ điều hành.

    a0 8: sh_link và sh_info = 8x 0: không áp dụng cho loại phần này. http://www.sco.com/developers/gabi/2003-12-17/ch4.sheader.html#special_silities

    b0 0: sh_addralign = 04 = TODO: tại sao cần căn chỉnh này? Có phải nó chỉ dành cho sh_addr và cả các ký tự bên trong sh_addr không?

    b0 8: sh_entsize = 00 = phần không chứa bảng. Nếu! = 0, điều này có nghĩa là phần này chứa một bảng các bản ghi có kích thước cố định. Trong tệp này, chúng ta có thể thấy từ đầu ra readelf rằng đây là trường hợp của các phần .symtab và .rela.text.

phần .text

Bây giờ chúng ta đã thực hiện một phần bằng tay, hãy hoàn thành và sử dụng bản thân -S của các phần khác.

Tên Loại Địa chỉ Chênh lệch Kích thước EntSize Cờ Liên kết Thông tin Liên kết Căn chỉnh [2] .text

Văn bản có thể thực thi nhưng không thể ghi: nếu chúng ta cố gắng viết mặc định Linux cho nó. Hãy xem nếu chúng ta thực sự có mã:

Obdump -d hello_world.o

Hello_world.o: định dạng tệp elf64-x86-64 Giải mã phần .text: 0000000000000000<_start>: 0: b8 01 00 00 00 mov $ 0x1,% eax 5: bf 01 00 00 00 mov $ 0x1,% edi a: 48 be 00 00 00 00 movabs $ 0x0,% rsi 11: 00 00 00 14: ba 0d 00 00 00 mov $ 0xd,% edx 19: 0f 05 syscall 1b: b8 3c 00 00 00 mov $ 0x3c,% eax 20: bf 00 00 00 00 mov $ 0x0,% edi 25: 0f 05 syscall

Nếu chúng ta có grep b8 01 00 00 trên hd, chúng ta thấy rằng nó chỉ xảy ra ở 00000210, đó là những gì phần này nói. Và kích thước là 27, cũng vừa vặn. Vì vậy, chúng ta phải nói về phần chính xác.

Điều này trông giống như mã chính xác: một lần viết theo sau là một lần thoát.

Phần thú vị nhất là dòng a, có:

Movabs $ 0x0,% rsi

chuyển địa chỉ của chuỗi vào lệnh gọi hệ thống. Hiện tại 0x0 chỉ là một trình giữ chỗ. Sau khi ràng buộc, nó sẽ thay đổi:

4000ba: 48 be d8 00 60 00 00 movabs $ 0x6000d8,% rsi

Có thể sửa đổi này do dữ liệu trong phần .rela.text.

SHT_STRTAB

Các phần có sh_type == SHT_STRTAB được gọi là bảng chuỗi.

Các phần như vậy được các phần khác sử dụng khi tên chuỗi được sử dụng. Phần Sử dụng cho biết:

  • họ sử dụng dòng nào
  • chỉ số trong bảng các hàng mục tiêu nơi hàng đó bắt đầu là gì

Vì vậy, ví dụ, chúng ta có thể có một bảng chuỗi chứa: TODO: chúng ta có nên bắt đầu bằng \ 0 không?

Dữ liệu: \ 0 a b c \ 0 d e f \ 0 Chỉ mục: 0 1 2 3 4 5 6 7 8

Và nếu phần khác muốn sử dụng chuỗi d e f thì họ phải trỏ đến chỉ số 5 của phần đó (chữ d).

Các bảng chuỗi đã biết:

  • .shstrtab
  • .strtab

.shstrtab

Loại phần: sh_type == SHT_STRTAB.

Tên thường gọi: dòng tiêu đề phần tiêu đề.

Tên phần.shstrtab được bảo lưu. Tiêu chuẩn cho biết:

Phần này chứa các tên phần.

Phần này chỉ định trường e_shstrnd của chính tiêu đề ELF.

Các chỉ số hàng của phần này được biểu thị bằng trường sh_name của tiêu đề phần biểu thị các hàng.

Phần này không chỉ định SHF_ALLOC, vì vậy nó sẽ không xuất hiện trong tệp thực thi.

Readelf -x .shstrtab hello_world.o

Kết xuất hệ thập lục phân của phần ".shstrtab": 0x00000000 002e6461 7461002e 74657874 002e7368 ..data..text..sh 0x00000010 73747274 6162002e 73796d74 6162002e strtab..symtab .. 0x00000020 73747e7465780061 strte 73747e746500.

Dữ liệu trong phần này ở định dạng cố định: http://www.sco.com/developers/gabi/2003-12-17/ch4.strtab.html

Nếu chúng ta nhìn vào các tên phần khác, chúng ta có thể thấy rằng tất cả chúng đều chứa số, ví dụ. phần .text được đánh số 7.

Sau đó, mỗi dòng kết thúc khi ký tự NUL đầu tiên được tìm thấy, ví dụ: ký tự 12 \ 0 ngay sau .text \ 0.

.symtab

Loại phần: sh_type == SHT_SYMTAB.

Tên thường gọi: bảng ký hiệu.

Đầu tiên hãy lưu ý rằng:

  • sh_link = 5
  • sh_info = 6

Trong phần SHT_SYMTAB, những con số này có nghĩa là:

  • Dây
  • cung cấp tên biểu tượng nằm trong phần 5, .strtab
  • dữ liệu di dời nằm trong phần 6, .rela.text

Một công cụ cấp cao tốt để tháo gỡ phần này:

Nm hello_world.o

mang lại:

0000000000000000 T _start 0000000000000000 d hello_world 000000000000000d a hello_world_len

Tuy nhiên, đây là một biểu diễn cấp cao bỏ qua một số loại ký tự nhất định và biểu thị các ký tự. Phân tích chi tiết hơn có thể được lấy bằng cách sử dụng:

Readelf -s hello_world.o

mang lại:

Bảng ký hiệu ".symtab" có 7 mục nhập: Số: Loại kích thước giá trị Ràng buộc Vis Ndx Tên 0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND 1: 00000000000000 0 FILE LOCAL DEFAULT ABS hello_world.asm 2: 0000000000000000 0 PHẦN ĐỊNH VỊ ĐỊA PHƯƠNG 1 3: 00000000000000 _

Định dạng nhị phân của bảng được ghi lại tại http://www.sco.com/developers/gabi/2003-12-17/ch4.symtab.html

Readelf -x .symtab hello_world.o

Đưa cái gì:

Kết xuất hex của phần ".symtab": 0x00000000 00000000 00000000 00000000 00000000 ................ 0x00000010 00000000 00000000 01000000 0400f1ff ................ . 0x00000020 00000000 000000 00000000 00000000 ................ 0x00000030 00000000 000000 00000000 000000 ................ ........ 0x00000050 00000000 00000000 00000000 00000000 ...... 0x00000070 00000000 00000000 1d000000 0000f1ff ................ 0x00000080 0d000000 00000000 00000000 00000000 ..... ........... 0x00000090 2d000000 10000200 00000000 00000000 -............. 0x000000a0 00000000 00000000 ........

Các mục nhập thuộc loại:

Typedef struct (Elf64_Word st_name; unsigned char st_info; unsigned char st_other; Elf64_Half st_shndx; Elf64_Addr st_value; Elf64_Xword st_size;) Elf64_Sym;

Giống như bảng phân vùng, mục nhập đầu tiên là ma thuật và được đặt thành các giá trị vô nghĩa cố định.

Mục 1 có ELF64_R_TYPE == STT_FILE. ELF64_R_TYPE tiếp tục bên trong st_info.

Phân tích byte:

    10 8: st_name = 01000000 = ký tự 1 trong .strtab, cho đến ký tự 0 tiếp theo hello_world.asm

    Trình liên kết có thể sử dụng phân đoạn này của tệp thông tin để xác định phân đoạn nào của phân đoạn sắp tới.

    10 12: st_info = 04

    Bits 0-3 = ELF64_R_TYPE = Type = 4 = STT_FILE: Mục đích chính của mục này là sử dụng st_name để chỉ định tên của tệp được tạo bởi tệp đối tượng này.

    Bit 4-7 = ELF64_ST_BIND = Ràng buộc = 0 = STB_LOCAL. Giá trị bắt buộc cho STT_FILE.

    10 13: st_shndx = Bảng ký hiệu Bảng tiêu đề Chỉ mục = f1ff = SHN_ABS. Bắt buộc đối với STT_FILE.

    20 0: st_value = 8x 00: bắt buộc đối với giá trị cho STT_FILE

    20 8: st_size = 8x 00: không có kích thước được phân bổ

Bây giờ, chúng tôi nhanh chóng giải thích phần còn lại.

STT_SECTION

Có hai phần tử như vậy, một phần tử trỏ tới .data và phần tử kia trỏ tới .text (chỉ mục phần 1 và 2).

Số: Kích thước giá trị Loại Ràng buộc Vis Ndx Tên 2: 0000000000000000 0 PHẦN ĐỊNH VỊ ĐỊA PHƯƠNG 1 3: 0000000000000000 0 PHẦN ĐỊNH VỊ ĐỊA PHƯƠNG 2

QUÁ, mục đích của họ là gì?

STT_NOTYPE

Sau đó nhập các ký tự quan trọng nhất:

Num: Loại kích thước giá trị Ràng buộc Vis Ndx Tên 4: 0000000000000000 0 NOTYPE LOCAL DEFAULT 1 hello_world 5: 000000000000000d 0 NOTYPE LOCAL DEFAULT ABS hello_world_len 6: 0000000000000000 bắt đầu NOTYPE GLOBAL _ Chuỗi 2

hello_world nằm trong phần .data (chỉ mục 1). Giá trị này là 0: nó trỏ đến byte đầu tiên của phần này.

Bắt đầu được đánh dấu bằng khả năng hiển thị TOÀN CẦU, như chúng tôi đã viết:

global_start

tại NASM. Điều này là cần thiết vì nó phải được coi là một điểm vào. Không giống như C, các nhãn NASM là cục bộ theo mặc định.

hello_world_len trỏ đến st_shndx đặc biệt == SHN_ABS == 0xF1FF.

0xF1FF được chọn để không xung đột với các phần khác.

st_value == 0xD == 13, là giá trị mà chúng tôi đã lưu trữ ở đó trên assembly: độ dài của chuỗi Hello World! .

Điều này có nghĩa là việc di chuyển sẽ không ảnh hưởng đến giá trị này: nó là một hằng số.

Đây là một tối ưu hóa nhỏ mà trình lắp ráp của chúng tôi thực hiện cho chúng tôi và có sự hỗ trợ của ELF.

Nếu chúng tôi sử dụng địa chỉ hello_world_len ở bất kỳ đâu, trình hợp dịch sẽ không đánh dấu nó là SHN_ABS và trình liên kết sẽ có thêm một lần di dời sau đó.

SHT_SYMTAB trong tệp thực thi

Theo mặc định, NASM đặt .symtab trong tệp thực thi.

Điều này chỉ được sử dụng để gỡ lỗi. Không có biểu tượng, chúng ta hoàn toàn mù tịt và phải thiết kế lại mọi thứ.

Bạn có thể xóa nó bằng objcopy và tệp thực thi sẽ vẫn hoạt động. Các tệp thực thi như vậy được gọi là tệp thực thi phân tách.

.strtab

Giữ chuỗi cho bảng ký tự.

Trong phần này, sh_type == SHT_STRTAB.

Trỏ tới sh_link == 5 của phần .symtab.

Readelf -x .strtab hello_world.o

Kết xuất hệ thập lục phân của phần ".strtab": 0x00000000 0068656c 6c6f5f77 6f726c64 2e61736d .hello_world.asm 0x00000010 0068656c 6c6f5f77 6f726c64 0068656c .hello_world.hel 0x0000004 6f6f700170017400 nghệ thuật 6f6f700170027400

Điều này có nghĩa là đó là một hạn chế ở mức ELF mà các biến toàn cục không được chứa các ký tự NUL.

.rela.text

Loại phần: sh_type == SHT_RELA.

Tên thường gọi: phần di chuyển.

Rela.text chứa dữ liệu tái định cư chỉ định cách địa chỉ sẽ được thay đổi khi tệp thực thi cuối cùng được liên kết. Điều này cho biết các byte của vùng văn bản sẽ được thay đổi khi liên kết xảy ra với các vị trí bộ nhớ chính xác.

Về cơ bản, nó chuyển đổi văn bản đối tượng có chứa địa chỉ trình giữ chỗ 0x0:

A: 48 là 00 00 00 00 00 movabs $ 0x0,% rsi 11:00 00 00

đến mã thực thi thực tế có chứa 0x6000d8 cuối cùng:

4000ba: 48 be d8 00 60 00 00 movabs $ 0x6000d8,% rsi 4000c1: 00 00 00

Sh_info = 6 của phần .symtab đã được chỉ định.

readelf -r hello_world.o cung cấp:

Phần chuyển vị trí ".rela.text" tại offset 0x3b0 chứa 1 mục: Offset Info Type Sym. Giá trị Sym. Tên + Phụ lục 00000000000c 000200000001 R_X86_64_64 0000000000000000 .data + 0

Phần không tồn tại trong tệp thực thi.

Số byte thực tế:

00000370 0c 00 00 00 00 00 00 00 01 00 00 00 02 00 00 00 | ................ | 00000380 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ |

Cấu trúc đại diện:

Typedef struct (Elf64_Addr r_offset; Elf64_Xword r_info; Elf64_Sxword r_addend;) Elf64_Rela;

    370 0: r_offset = 0xC: address to address.text có địa chỉ sẽ được thay đổi

    370 8: r_info = 0x200000001. Chứa 2 trường:

    • ELF64_R_TYPE = 0x1: Giá trị phụ thuộc vào kiến ​​trúc chính xác.
    • ELF64_R_SYM = 0x2: Chỉ mục của phân vùng được trỏ đến bởi địa chỉ, do đó .data, nằm ở chỉ mục 2.

    AMD64 ABI cho biết loại 1 được gọi là R_X86_64_64 và nó đại diện cho hoạt động S + A trong đó:

    • S: giá trị của biểu tượng trong tệp đối tượng, ở đây là 0 vì chúng tôi đang trỏ đến 00 00 00 00 00 00 00 00 từ movabs $ 0x0,% rsi
    • a: phần bổ sung có trong trường r_added

    Địa chỉ này được thêm vào phân vùng nơi quá trình di chuyển đang diễn ra.

    Thao tác di chuyển này hoạt động trên 8 byte.

    380 0: r_addend = 0

Do đó, trong ví dụ của chúng tôi, chúng tôi kết luận rằng địa chỉ mới sẽ là: S + A = .data + 0, và do đó là địa chỉ đầu tiên trong phần dữ liệu.

Bảng Tiêu đề Chương trình

Chỉ hiển thị trong tệp thực thi.

Chứa thông tin về cách tệp thực thi sẽ được đặt trong bộ nhớ ảo của quy trình.

Tệp thực thi được tạo bởi tệp đối tượng trình liên kết. Các tác vụ chính mà trình liên kết thực hiện:

    xác định phần nào của tệp đối tượng đi vào phần nào của tệp thực thi.

    Trong Binutils, nó đi đến việc phân tích cú pháp tập lệnh trình tạo và làm việc với rất nhiều giá trị mặc định.

    Bạn có thể lấy tập lệnh trình liên kết được sử dụng với ld --verbose và cài đặt tập lệnh tùy chỉnh với ld -T.

    điều hướng qua các phần văn bản. Nó phụ thuộc vào cách nhiều phân vùng phù hợp với bộ nhớ.

readelf -l hello_world.out cung cấp:

Elf file type is EXEC (Executable file) Entry point 0x4000b0 There are 2 program headers, starting at offset 64 Program Headers: Type Offset VirtAddr PhysAddr FileSiz MemSiz Flags Align LOAD 0x0000000000000000 0x0000000000400000 0x0000000000400000 0x00000000000000d7 0x00000000000000d7 R E 200000 LOAD 0x00000000000000d8 0x00000000006000d8 0x00000000006000d8 0x000000000000000d 0x000000000000000d RW 200000 Section tới ánh xạ phân đoạn: Phân đoạn các phần ... 00 .text 01 .data

Trong tiêu đề ELF e_phoff, e_phnum và e_phentsize cho chúng tôi biết rằng có 2 tiêu đề chương trình bắt đầu ở 0x40 và mỗi tiêu đề là 0x38 byte, vì vậy chúng là:

00000040 01 00 00 00 05 00 00 00 00 00 00 00 00 00 00 00 |................| 00000050 00 00 40 00 00 00 00 00 00 00 40 00 00 00 00 00 |[email được bảo vệ]@ ..... | 00000060 d7 00 00 00 00 00 00 00 00 d7 00 00 00 00 00 00 00 | ................ | 00000070 00 00 20 00 00 00 00 00 | .. ..... |

00000070 01 00 00 00 06 00 00 00 | ........ | 00000080 d8 00 00 00 00 00 00 00 00 d8 00 60 00 00 00 00 00 | .......... `..... | 00000090 d8 00 60 00 00 00 00 00 0d 00 00 00 00 00 00 00 | ..` ............. | 000000a0 0d 00 00 00 00 00 00 00 00 00 00 20 00 00 00 00 00 | .......... ..... | typedef struct (Elf64_Word p_type; Elf64_Word p_flags; Elf64_Off p_offset; Elf64_Addr p_vaddr; Elf64_Addr p_paddr; Elf64_Xword p_filesz; Elf64_Xword p_memsz; Elf64Phdr)

Sự cố của phần đầu tiên:

  • 40 0: p_type = 01 00 00 00 = PT_LOAD: TODO. Tôi nghĩ điều này có nghĩa là nó sẽ được tải vào bộ nhớ. Các loại khác có thể không nhất thiết phải như vậy.
  • 40 4: p_flags = 05 00 00 00 = quyền thực thi và đọc, không ghi TODO
  • 40 8: p_offset = 8x 00 VIỆC CẦN LÀM: đây là gì? Có vẻ như sự chênh lệch so với đầu các phân đoạn. Nhưng điều đó có nghĩa là một số phân đoạn được đan xen vào nhau? Bạn có thể chơi với nó một chút: gcc -Wl, -Ttext-segment = 0x400030 hello_world.c
  • 50 0: p_vaddr = 00 00 40 00 00 00 00 00: bắt đầu địa chỉ bộ nhớ ảo để tải phân đoạn này vào
  • 50 8: p_paddr = 00 00 40 00 00 00 00 00: địa chỉ vật lý bắt đầu tải vào bộ nhớ. Chỉ các câu hỏi dành cho các hệ thống mà chương trình có thể đặt địa chỉ vật lý. Nếu không, như với hệ thống System V, bất cứ điều gì có thể xảy ra. Có vẻ như NASM sẽ chỉ sao chép p_vaddrr
  • 60 0: p_filesz = d7 00 00 00 00 00 00 00: VIỆC LÀM vs p_memsz
  • 60 8: p_memsz = d7 00 00 00 00 00 00 00: VIỆC CẦN LÀM
  • 70 0: p_align = 00 00 20 00 00 00 00 00: 0 hoặc 1 có nghĩa là không cần căn chỉnh CẦN LÀM, điều đó có nghĩa là gì? nếu không thì dư thừa với các trường khác

Thứ hai cũng tương tự.

Ánh xạ phần tới phân đoạn:

phần tự đọc cho chúng ta biết rằng:

  • 0 - segment.text. Đúng vậy, đó là lý do tại sao nó có thể thực thi và không thể ghi.
  • 1 - phân đoạn. Dữ liệu.

Bài tương tự