Làm thế nào để chúng ta đi từ lắp ráp đến mã máy (tạo mã)


16

Có cách nào dễ dàng để hình dung bước giữa lắp ráp mã thành mã máy không?

Ví dụ: nếu bạn mở về một tệp nhị phân trong notepad, bạn sẽ thấy một biểu diễn được định dạng bằng văn bản của mã máy. Tôi giả sử rằng mỗi byte (ký hiệu) bạn thấy là ký tự ascii tương ứng cho giá trị nhị phân của nó?

Nhưng làm thế nào để chúng ta đi từ lắp ráp đến nhị phân, những gì đang xảy ra đằng sau hậu trường ??

Câu trả lời:


28

Nhìn vào tài liệu tập lệnh và bạn sẽ tìm thấy các mục như thế này từ một vi điều khiển pic cho mỗi lệnh:

ví dụ hướng dẫn addlw

Dòng "mã hóa" cho biết lệnh đó trông như thế nào trong nhị phân. Trong trường hợp này, nó luôn bắt đầu bằng 5 cái, sau đó là một bit không quan tâm (có thể là một hoặc không), sau đó là chữ "k" cho nghĩa đen mà bạn đang thêm.

Một vài bit đầu tiên được gọi là "opcode", là duy nhất cho mỗi lệnh. Về cơ bản, CPU nhìn vào opcode để xem nó là hướng dẫn gì, sau đó nó biết giải mã các chữ "k" dưới dạng một số được thêm vào.

Thật tẻ nhạt, nhưng không khó để mã hóa và giải mã. Tôi đã có một lớp học đại học, nơi chúng tôi phải làm điều đó bằng tay trong các kỳ thi.

Để thực sự tạo một tệp thực thi đầy đủ, bạn cũng phải thực hiện những việc như phân bổ bộ nhớ, tính toán bù nhánh và đặt nó vào định dạng như ELF , tùy thuộc vào hệ điều hành của bạn.


10

Phần lớn các opcodes hội có sự tương ứng một-một với các hướng dẫn máy bên dưới. Vì vậy, tất cả những gì bạn phải làm là xác định từng opcode bằng ngôn ngữ lắp ráp, ánh xạ nó tới hướng dẫn máy tương ứng và viết hướng dẫn máy ra một tệp, cùng với các tham số tương ứng của nó (nếu có). Sau đó, bạn lặp lại quy trình cho từng opcode bổ sung trong tệp nguồn.

Tất nhiên, cần nhiều hơn thế để tạo một tệp thực thi sẽ tải và chạy đúng trên hệ điều hành, và hầu hết các trình biên dịch hợp lý đều có một số khả năng bổ sung ngoài việc ánh xạ opcodes đơn giản theo hướng dẫn của máy (ví dụ như macro).


7

Điều đầu tiên bạn cần là một cái gì đó giống như tập tin này . Đây là cơ sở dữ liệu hướng dẫn cho các bộ xử lý x86 được sử dụng bởi trình biên dịch NASM (mà tôi đã giúp viết, mặc dù không phải là các phần thực sự dịch các hướng dẫn). Cho phép chọn một dòng tùy ý từ cơ sở dữ liệu:

ADD   rm32,imm8    [mi:    hle o32 83 /0 ib,s]      386,LOCK

Điều này có nghĩa là nó mô tả hướng dẫn ADD. Có nhiều biến thể của hướng dẫn này và biến thể cụ thể được mô tả ở đây là biến thể lấy thanh ghi 32 bit hoặc địa chỉ bộ nhớ và thêm giá trị 8 bit ngay lập tức (nghĩa là hằng số được bao gồm trực tiếp trong lệnh). Một hướng dẫn lắp ráp ví dụ sẽ sử dụng phiên bản này là:

add eax, 42

Bây giờ, bạn cần lấy đầu vào văn bản của bạn và phân tích nó thành các lệnh và toán hạng riêng lẻ. Đối với hướng dẫn ở trên, điều này có thể sẽ dẫn đến một cấu trúc có chứa lệnh ADDvà một mảng các toán hạng (tham chiếu đến thanh ghi EAXvà giá trị 42). Khi bạn có cấu trúc này, bạn chạy qua cơ sở dữ liệu hướng dẫn và tìm dòng phù hợp với cả tên lệnh và loại toán hạng. Nếu bạn không tìm thấy kết quả khớp, đó là lỗi cần được trình bày cho người dùng ("sự kết hợp bất hợp pháp của opcode và toán hạng" hoặc tương tự là văn bản thông thường).

Khi chúng tôi đã nhận được dòng từ cơ sở dữ liệu, chúng tôi xem xét cột thứ ba, hướng dẫn này là:

[mi:    hle o32 83 /0 ib,s] 

Đây là một bộ hướng dẫn mô tả cách tạo hướng dẫn mã máy cần thiết:

  • Đây milà một mô tả của các toán hạng: một toán hạng modr/m(thanh ghi hoặc bộ nhớ) (có nghĩa là chúng ta sẽ cần nối thêm một modr/mbyte vào cuối hướng dẫn, chúng ta sẽ đến sau) và một lệnh ngay lập tức (sẽ được sử dụng trong mô tả của hướng dẫn).
  • Tiếp theo là hle. Điều này xác định cách chúng tôi xử lý tiền tố "khóa". Chúng tôi đã không sử dụng "khóa", vì vậy chúng tôi bỏ qua nó.
  • Tiếp theo là o32. Điều này cho chúng ta biết rằng nếu chúng ta lắp ráp mã cho định dạng đầu ra 16 bit, thì lệnh cần một tiền tố ghi đè kích thước toán hạng. Nếu chúng tôi đang tạo đầu ra 16 bit, chúng tôi sẽ tạo tiền tố ngay bây giờ ( 0x66), nhưng tôi sẽ cho rằng chúng tôi không và tiếp tục.
  • Tiếp theo là 83. Đây là một byte theo nghĩa đen trong hệ thập lục phân. Chúng tôi đầu ra nó.
  • Tiếp theo là /0. Điều này xác định một số bit bổ sung mà chúng ta sẽ cần trong bytem modr / m và khiến chúng ta tạo ra nó. Các modr/mbyte được sử dụng để đăng ký mã hóa hoặc tài liệu tham khảo bộ nhớ gián tiếp. Chúng tôi có một toán hạng như vậy, một thanh ghi. Thanh ghi có một số, được chỉ định trong tệp dữ liệu khác :

    eax     REG_EAX         reg32           0
  • Chúng tôi kiểm tra xem reg32 đồng ý với kích thước yêu cầu của hướng dẫn từ cơ sở dữ liệu gốc không. Số 0đăng ký. Một modr/mbyte là cấu trúc dữ liệu được chỉ định bởi bộ xử lý, trông như thế này:

     (most significant bit)
     2 bits       mod    - 00 => indirect, e.g. [eax]
                           01 => indirect plus byte offset
                           10 => indirect plus word offset
                           11 => register
     3 bits       reg    - identifies register
     3 bits       rm     - identifies second register or additional data
     (least significant bit)
  • Bởi vì chúng tôi đang làm việc với một đăng ký, modlĩnh vực này là 0b11.

  • Các reglĩnh vực là số sổ đăng ký, chúng tôi đang sử dụng,0b000
  • Bởi vì chỉ có một đăng ký trong hướng dẫn này, chúng tôi cần điền vào rm trường với một cái gì đó. Đó là những gì dữ liệu bổ sung được chỉ định /0là dành cho, vì vậy chúng tôi đưa dữ liệu đó vào rmtrường , 0b000.
  • Các modr/m vì thế byte là 0b11000000hay 0xC0. Chúng tôi đầu ra này.
  • Tiếp theo là ib,s . Điều này xác định một byte ngay lập tức đã ký. Chúng tôi xem xét các toán hạng và lưu ý rằng chúng tôi có sẵn một giá trị ngay lập tức. Chúng tôi chuyển đổi nó thành một byte đã ký và xuất ra nó ( 42=> 0x2A).

Do đó, hướng dẫn lắp ráp hoàn chỉnh là : 0x83 0xC0 0x2A. Gửi nó đến mô-đun đầu ra của bạn, cùng với một lưu ý rằng không có byte nào tạo thành các tham chiếu bộ nhớ (mô-đun đầu ra có thể cần biết nếu chúng làm như vậy).

Lặp lại cho mọi hướng dẫn. Theo dõi nhãn để bạn biết phải chèn gì khi chúng được tham chiếu. Thêm phương tiện cho các macro và chỉ thị được truyền đến các mô-đun đầu ra tệp đối tượng của bạn. Và đây là cách cơ bản một trình biên dịch hoạt động.


1
Cảm ơn bạn. Giải thích tuyệt vời nhưng không nên là "0x83 0xC0 0x2A" thay vì "0x83 0xB0 0x2A" vì 0b11000000 = 0xC0
Kamran

@Kamran - $ cat > test.asm bits 32 add eax,42 $ nasm -f bin test.asm -o test.bin $ od -t x1 test.bin 0000000 83 c0 2a 0000003... vâng, bạn hoàn toàn đúng. :)
Jules

2

Trong thực tế, một lắp ráp thường không sản xuất trực tiếp một số nhị phân thực thi , nhưng một số đối tượng tập tin (để được cho ăn sau đó để các mối liên kết ). Tuy nhiên, có những trường hợp ngoại lệ (bạn có thể sử dụng một số trình biên dịch để tạo trực tiếp một số thực thi nhị phân; chúng không phổ biến).

Đầu tiên, lưu ý rằng nhiều nhà lắp ráp là các chương trình phần mềm miễn phí ngày nay . Vì vậy, tải xuống và biên dịch trên máy tính của bạn mã nguồn của GNU dưới dạng (một phần của binutils ) và của nasm . Sau đó nghiên cứu mã nguồn của họ. BTW, tôi khuyên bạn nên sử dụng Linux cho mục đích đó (nó là một hệ điều hành rất thân thiện với nhà phát triển và phần mềm miễn phí).

Tệp đối tượng được tạo bởi trình biên dịch chương trình chứa đáng chú ý là một đoạn mã và hướng dẫn di chuyển . Nó được tổ chức theo định dạng tệp tài liệu tốt, phụ thuộc vào hệ điều hành. Trên Linux, định dạng đó (được sử dụng cho các tệp đối tượng, thư viện dùng chung, kết xuất lõi và tệp thực thi) là ELF . Tệp đối tượng đó sau đó được nhập vào trình liên kết (cuối cùng tạo ra một tệp thực thi). Định vị lại được chỉ định bởi ABI (ví dụ x86-64 ABI ). Đọc cuốn sách Trình liên kết và bộ nạp của Levine để biết thêm.

Đoạn mã trong tệp đối tượng như vậy chứa mã máy có lỗ (được điền, với sự trợ giúp của thông tin di dời, bởi trình liên kết). Mã máy (có thể định vị lại) được tạo bởi trình biên dịch chương trình rõ ràng là đặc trưng cho kiến trúc tập lệnh . Các x86 hoặc x86-64 (được sử dụng trong hầu hết các bộ xử lý máy tính xách tay hoặc máy tính để bàn) rất phức tạp trong các chi tiết của chúng. Nhưng một tập hợp con đơn giản hóa, được gọi là y86 hoặc y86-64, đã được phát minh cho mục đích giảng dạy. Đọc slide trên chúng. Các câu trả lời khác cho câu hỏi này cũng giải thích một chút về điều đó. Bạn có thể muốn đọc một cuốn sách hay về Kiến trúc máy tính .

Hầu hết các nhà lắp ráp đang làm việc trong hai lần , lần thứ hai phát ra di chuyển hoặc sửa một số đầu ra của lần đầu tiên. Bây giờ họ sử dụng các kỹ thuật phân tích cú pháp thông thường (vì vậy có lẽ đọc Sách Rồng ).

Làm thế nào một thực thi được bắt đầu bởi nhân hệ điều hành (ví dụ như cách execvegọi hệ thống hoạt động trên Linux) là một câu hỏi khác nhau (và phức tạp). Nó thường thiết lập một số không gian địa chỉ ảo (trong quá trình thực hiện (2) ...) sau đó xác định lại trạng thái bên trong của quy trình (bao gồm các thanh ghi chế độ người dùng ). Một trình liên kết động - như ld-linux.so (8) trên Linux - có thể được tham gia vào thời gian chạy. Đọc một cuốn sách hay, chẳng hạn như Hệ điều hành: Ba mảnh dễ dàng . Các OSDEV wiki cũng được cung cấp thông tin hữu ích.

Tái bút Câu hỏi của bạn rất rộng đến nỗi bạn cần phải đọc một vài cuốn sách về nó. Tôi đã đưa ra một số tài liệu tham khảo (rất không đầy đủ). Bạn nên tìm thêm trong số họ.


1
Về định dạng tệp đối tượng, đối với người mới bắt đầu, tôi khuyên bạn nên xem định dạng RDOFF do NASM sản xuất. Điều này được cố ý thiết kế đơn giản đến mức có thể thực tế và vẫn hoạt động trong nhiều tình huống khác nhau. Nguồn NASM bao gồm một trình liên kết và trình tải cho định dạng. (Tiết lộ đầy đủ - Tôi đã thiết kế và viết tất cả những thứ này)
Jules
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.