Hướng Dẫn Assembly 64bit (Tái Bản)

2736

Bài viết được sự cho phép của tác giả Trần Thiện Khiêm

Nhiều bạn hỏi mình lập trình viên nên học ngôn ngữ gì, hôm nay mình xin giới thiệu một ngôn ngữ khá quen thuộc..

LỜI NÓI ĐẦU

Suốt hàng ngàn năm, con người đã sử dụng các ngôn ngữ lập trình để bắt máy tính phải “hiểu” mình và làm theo mệnh lệnh của mình. Các bạn đừng bị thị trường lừa dối, bởi vì máy tính vốn chỉ hiểu một ngôn ngữ duy nhất, đó là ngôn ngữ máy (machine code). Và trong khi các bạn diễn giải ý định của mình bằng java hay c# thì máy tính hoàn toàn không hiểu được. Và phải qua các bước biên dịch phức tạp thì máy mới hiểu được bạn muốn nói gì. Nhưng mà “tam sao thất bản”, chắc chắn máy tính sẽ không hiểu đúng 100% bạn ý định nói gì đâu. Mà coi như là máy tính hiểu bạn rồi, thì khi nào bạn sẽ hiểu máy tính?

Nếu bạn là người yêu máy tính, thích lập trình thì hãy nhanh chân học ngôn ngữ mà máy cũng hiểu và bạn cũng hiểu. Mình sẽ không bắt các bạn phải học mã máy, nhưng chí ít bạn nên học Assemb-Ly vì với máy tính, như thế là tuyệt lắm rồi. Assembly – Machine code nó tương đương 1-1 nên nếu biết assembly thì cũng tạm chấp nhận.

Đối với các bạn học bách khoa thì quả thật là may mắn vì các bạn đã được học assembly. Nhưng mà chắc là đã quên hết rồi nên trong bài này mình sẽ hướng dẫn lại căn bản cho các bạn.

Trong bài viết sử dụng MacOS và Xcode để lập trình nên bạn nào không có thì sử dụng trí tưởng tượng phong phú của mình.

CĂN BẢN ASSEMBLY

Data size: Các khái niệm cơ bản về kích thước của dữ liệu khi làm việc: byte (8 bit) -> word (16 bit) -> double words (32 bit) -> quad words 64 bit). Mình chỉ làm việc với 2 toán tử có kích thước tương đương.

Thanh ghi: Là các thanh nhớ đặc biệt dùng để tính toán. Khi tính toán thì chỉ thao tác với giá trị trên thanh ghi. (các thanh ghi general các bạn từng học như ax <=> al: (8 bit); ax: (16 bit); eax (32 bit); rax (64 bit))

Lưu trữ dữ liệu: Ngoài thanh ghi, có 2 vùng nhớ là stack và heap.

Lệnh: có các nhóm lệnh: đọc ghi dữ liệu / tính toán / điều khiển (lệnh nhảy – gọi hàm).

Nhãn (label): Mình có thể đánh dấu các câu lệnh, vùng nhớ bằng nhãn để có thể tham chiếu sau này.

Cú pháp: [<nhãn>:] <mã lệnh> nguồn[, đích]

Ví dụ:

movl $1, %eax; // gán eax = 1

move $1 to %eax (l nghĩa là kiểu long (4 bytes), b = bytes, w = word (2 bytes), q = quad words (8 bytes))

assembly 64bit

HELLO WORLD

Để bắt đầu, các bạn mở Xcode và tạo 1 dự án Command Line Tool, chọn ngôn ngữ là C. Bước tiếp theo, đổi tên main.c thành main.s và xoá hết code C trong main.s. .s là file chứa mã nguồn assembly.

Lúc biên dịch, bạn sẽ nhận được một thông báo lỗi như sau:

Undefined symbols for architecture x86_64:

 “_main”, referenced from:

    implicit entry/start for main executable

Thông báo lỗi này do chưa viết hàm main.

Chương trình assembly được chia thành nhiều đoạn (segment), cấu trúc căn bản như sau:

.section __DATA, __data // section data chứa data sử dụng trong chương trình

.section __TEXT, __text // section text chứa code trong chương trình

Chương trình muốn chạy được thì phải có symbol _main, do đó ta thêm symbol _main vào trong chương trình.

.section __DATA, __data // section that stores data

.section __TEXT, __text // section that stores executable code

.global _main

_main:

Chỉ thị global _main đăng kí với trình biên dịch symbol _main sẽ được public trong giai đoạn liên kết.

Bây giờ chúng ta đã biên dịch thành công, tuy nhiên lúc chạy chương trình sẽ gặp lỗi sau:

Thread 1: EXC_BAD_ACCESS

Hàm main chưa có gì thì làm sao mà chạy? làm sao mà chạy?

Lý do là hàm main hiện tại chúng ta chưa viết gì cả. Vậy hàm main sẽ làm gì? Chúng ta sẽ thử ra chỉ thị kết thúc chương trình.

_main:

movl $0x2000001, %eax

syscall

Nếu như trong MS DOS các bạn quen với interrupt (ngắt hệ thống) thì ở hệ thống 64 bit đó là syscall, các chương trình con của hệ thống. $0x2000001 là system exit(). Các bạn có thể tham khảo các chương trình con này ở

http://opensource.apple.com//source/xnu/xnu-1228.5.20/bsd/sys/syscall.h.

– Định nghĩa + tham số tương đương ở đây: http://opensource.apple.com//source/xnu/xnu-1504.3.12/bsd/kern/syscalls.master

Như vậy chương trình của chúng ta đã chạy. Nhưng nó kết thúc ngay lập tức chưa kịp làm gì cả. Vậy làm sao để in ra chữ HelloWorld?

Nhìn trong file mình vưà gửi thì syscall 4 sẽ in viết một thông điệp ra dòng xuất chuẩn. Có 3 tham số : 1 là kí hiệu của dòng xuất (output stream) và 2 là thông điệp, 3 là kích thước của thông điệp

Ta chỉnh sửa lại chương trình như sau:

.section __DATA, __data

 helloMessage: .asciz "Hello Worldn"

.section __TEXT, __text

.global _main

_main:

 movl $0x2000004, %eax // the print syscall function

 movl $1, %edi // standard output = 1

 movq helloMessage@GOTPCREL(%rip), %rsi // address to message

 movq $100, %rdx // size of the message

 syscall 

 movl $0x2000001, %eax // the exit syscall function

 movl $0, %ebx // exit code == 0

 syscall

Ta vừa thêm dữ liệu chứa dòng helloMessage vào segment data với nội dung là “Hello Worldn”. Lúc biên dịch, nội dung này cũng sẽ được copy vào file thực thi, và lúc chạy sẽ hiện được tải vào data segment.

Để lấy địa chỉ của helloMessage, ta sử dụng cú pháp helloMessage@GOTPCREL(%rip), tương tự với toán tử lấy địa chỉ trong C: &helloMessage, sau đó ta gán vào tham số thứ 2, thanh ghi %rsi. Tham số thứ 3 là kích thước của thông điệp, ghi đại 0x100.

Sau khi gọi chương trình con số 4 của syscall, màn hình sẽ in ra dòng “Hello World”. Sau đó ta gọi chương trình con số 1 của syscall để kết thúc chương trình. Vậy là ta đã có chương trình “Hello World” đầu tiên. Xin chúc mừng!

!!!. Bây giờ bạn có thể yên tâm lên LinkedIn và cập nhật CV master Assembly.

Hẹn các bạn trong những bài viết tiếp theo về Assembly.

Bài viết gốc được đăng tải tại fanpage Khiem Tran – Programmer

Xem thêm:

Đừng bỏ lỡ việc làm IT hàng đầu dành cho Top Devlopers tại TopDev!